summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Adrian Roos <roosa@google.com> 2017-05-31 04:33:33 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2017-05-31 04:33:39 +0000
commit390ab841e25106e74c39240858972d3ecb8de652 (patch)
tree301dae16c192a97f63b3e841dd601716dc1c8cfa
parent9ff881ad72694e77e13978340aa7c4b5d6d41741 (diff)
parent7374d3a4bca6bfbf7da1ef5dbf0db9f35f0c8315 (diff)
Merge "Credential FRP: Add implementation"
-rw-r--r--core/java/android/app/KeyguardManager.java85
-rw-r--r--core/java/android/service/gatekeeper/IGateKeeperService.aidl6
-rw-r--r--core/java/android/service/persistentdata/IPersistentDataBlockService.aidl1
-rw-r--r--core/java/com/android/internal/widget/ILockSettings.aidl5
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java54
-rw-r--r--core/java/com/android/internal/widget/VerifyCredentialResponse.java28
-rw-r--r--core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java45
-rw-r--r--services/core/java/com/android/server/PersistentDataBlockManagerInternal.java29
-rw-r--r--services/core/java/com/android/server/PersistentDataBlockService.java102
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsService.java250
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsStorage.java168
-rw-r--r--services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java73
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java43
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java84
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/MockGateKeeperService.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java112
18 files changed, 981 insertions, 116 deletions
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index fcf0aab0c821..4a0b644d758a 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -28,11 +28,12 @@ import android.content.pm.ResolveInfo;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.UserHandle;
+import android.provider.Settings;
+import android.service.persistentdata.IPersistentDataBlockService;
import android.util.Log;
import android.view.IOnKeyguardExitResult;
import android.view.IWindowManager;
@@ -40,6 +41,7 @@ import android.view.WindowManager.LayoutParams;
import android.view.WindowManagerGlobal;
import com.android.internal.policy.IKeyguardDismissCallback;
+import com.android.internal.widget.LockPatternUtils;
import java.util.List;
@@ -74,6 +76,13 @@ public class KeyguardManager {
"android.app.action.CONFIRM_DEVICE_CREDENTIAL_WITH_USER";
/**
+ * Intent used to prompt user for factory reset credentials.
+ * @hide
+ */
+ public static final String ACTION_CONFIRM_FRP_CREDENTIAL =
+ "android.app.action.CONFIRM_FRP_CREDENTIAL";
+
+ /**
* A CharSequence dialog title to show to the user when used with a
* {@link #ACTION_CONFIRM_DEVICE_CREDENTIAL}.
* @hide
@@ -88,6 +97,23 @@ public class KeyguardManager {
public static final String EXTRA_DESCRIPTION = "android.app.extra.DESCRIPTION";
/**
+ * A CharSequence description to show to the user on the alternate button when used with
+ * {@link #ACTION_CONFIRM_FRP_CREDENTIAL}.
+ * @hide
+ */
+ public static final String EXTRA_ALTERNATE_BUTTON_LABEL =
+ "android.app.extra.ALTERNATE_BUTTON_LABEL";
+
+ /**
+ * Result code returned by the activity started by
+ * {@link #createConfirmFactoryResetCredentialIntent} indicating that the user clicked the
+ * alternate button.
+ *
+ * @hide
+ */
+ public static final int RESULT_ALTERNATE = 1;
+
+ /**
* Get an intent to prompt the user to confirm credentials (pin, pattern or password)
* for the current user of the device. The caller is expected to launch this activity using
* {@link android.app.Activity#startActivityForResult(Intent, int)} and check for
@@ -130,6 +156,63 @@ public class KeyguardManager {
return intent;
}
+ /**
+ * Get an intent to prompt the user to confirm credentials (pin, pattern or password)
+ * for the previous owner of the device. The caller is expected to launch this activity using
+ * {@link android.app.Activity#startActivityForResult(Intent, int)} and check for
+ * {@link android.app.Activity#RESULT_OK} if the user successfully completes the challenge.
+ *
+ * @param alternateButtonLabel if not empty, a button is provided with the given label. Upon
+ * clicking this button, the activity returns
+ * {@link #RESULT_ALTERNATE}
+ *
+ * @return the intent for launching the activity or null if the credential of the previous
+ * owner can not be verified (e.g. because there was none, or the device does not support
+ * verifying credentials after a factory reset, or device setup has already been completed).
+ *
+ * @hide
+ */
+ public Intent createConfirmFactoryResetCredentialIntent(
+ CharSequence title, CharSequence description, CharSequence alternateButtonLabel) {
+ if (!LockPatternUtils.frpCredentialEnabled()) {
+ Log.w(TAG, "Factory reset credentials not supported.");
+ return null;
+ }
+
+ // Cannot verify credential if the device is provisioned
+ if (Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 0) != 0) {
+ Log.e(TAG, "Factory reset credential cannot be verified after provisioning.");
+ return null;
+ }
+
+ // Make sure we have a credential
+ try {
+ IPersistentDataBlockService pdb = IPersistentDataBlockService.Stub.asInterface(
+ ServiceManager.getService(Context.PERSISTENT_DATA_BLOCK_SERVICE));
+ if (pdb == null) {
+ Log.e(TAG, "No persistent data block service");
+ return null;
+ }
+ if (!pdb.hasFrpCredentialHandle()) {
+ Log.i(TAG, "The persistent data block does not have a factory reset credential.");
+ return null;
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ Intent intent = new Intent(ACTION_CONFIRM_FRP_CREDENTIAL);
+ intent.putExtra(EXTRA_TITLE, title);
+ intent.putExtra(EXTRA_DESCRIPTION, description);
+ intent.putExtra(EXTRA_ALTERNATE_BUTTON_LABEL, alternateButtonLabel);
+
+ // explicitly set the package for security
+ intent.setPackage(getSettingsPackageForIntent(intent));
+
+ return intent;
+ }
+
private String getSettingsPackageForIntent(Intent intent) {
List<ResolveInfo> resolveInfos = mContext.getPackageManager()
.queryIntentActivities(intent, PackageManager.MATCH_SYSTEM_ONLY);
diff --git a/core/java/android/service/gatekeeper/IGateKeeperService.aidl b/core/java/android/service/gatekeeper/IGateKeeperService.aidl
index 6db2110ccbc1..abc6466e6d15 100644
--- a/core/java/android/service/gatekeeper/IGateKeeperService.aidl
+++ b/core/java/android/service/gatekeeper/IGateKeeperService.aidl
@@ -78,4 +78,10 @@ interface IGateKeeperService {
* @param uid the Android user id.
*/
void clearSecureUserId(int uid);
+
+ /**
+ * Notifies gatekeeper that device setup has been completed and any potentially still existing
+ * state from before a factory reset can be cleaned up (if it has not been already).
+ */
+ void reportDeviceSetupComplete();
}
diff --git a/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl b/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
index 626b40827334..31352f14fe08 100644
--- a/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
+++ b/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
@@ -36,5 +36,6 @@ interface IPersistentDataBlockService {
void setOemUnlockEnabled(boolean enabled);
boolean getOemUnlockEnabled();
int getFlashLockState();
+ boolean hasFrpCredentialHandle();
}
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index b8c062e17645..ee16ab609a2a 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -28,7 +28,7 @@ interface ILockSettings {
boolean getBoolean(in String key, in boolean defaultValue, in int userId);
long getLong(in String key, in long defaultValue, in int userId);
String getString(in String key, in String defaultValue, in int userId);
- void setLockCredential(in String credential, int type, in String savedCredential, int userId);
+ void setLockCredential(in String credential, int type, in String savedCredential, int requestedQuality, int userId);
void resetKeyStore(int userId);
VerifyCredentialResponse checkCredential(in String credential, int type, int userId,
in ICheckCredentialProgressCallback progressCallback);
@@ -49,6 +49,7 @@ interface ILockSettings {
long addEscrowToken(in byte[] token, int userId);
boolean removeEscrowToken(long handle, int userId);
boolean isEscrowTokenActive(long handle, int userId);
- boolean setLockCredentialWithToken(String credential, int type, long tokenHandle, in byte[] token, int userId);
+ boolean setLockCredentialWithToken(String credential, int type, long tokenHandle,
+ in byte[] token, int requestedQuality, int userId);
void unlockUserWithToken(long tokenHandle, in byte[] token, int userId);
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 51aef73764b4..d476ea065360 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -27,6 +27,7 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.AsyncTask;
+import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -34,6 +35,7 @@ import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.IStorageManager;
@@ -65,6 +67,8 @@ public class LockPatternUtils {
private static final String TAG = "LockPatternUtils";
private static final boolean DEBUG = false;
+ private static final boolean FRP_CREDENTIAL_ENABLED =
+ Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.frpcredential.enable", false);
/**
* The key to identify when the lock pattern enabled flag is being accessed for legacy reasons.
@@ -112,6 +116,11 @@ public class LockPatternUtils {
public static final int CREDENTIAL_TYPE_PASSWORD = 2;
+ /**
+ * Special user id for triggering the FRP verification flow.
+ */
+ public static final int USER_FRP = UserHandle.USER_NULL + 1;
+
@Deprecated
public final static String LOCKOUT_PERMANENT_KEY = "lockscreen.lockedoutpermanently";
public final static String LOCKOUT_ATTEMPT_DEADLINE = "lockscreen.lockoutattemptdeadline";
@@ -295,24 +304,39 @@ public class LockPatternUtils {
}
public void reportFailedPasswordAttempt(int userId) {
+ if (userId == USER_FRP && frpCredentialEnabled()) {
+ return;
+ }
getDevicePolicyManager().reportFailedPasswordAttempt(userId);
getTrustManager().reportUnlockAttempt(false /* authenticated */, userId);
}
public void reportSuccessfulPasswordAttempt(int userId) {
+ if (userId == USER_FRP && frpCredentialEnabled()) {
+ return;
+ }
getDevicePolicyManager().reportSuccessfulPasswordAttempt(userId);
getTrustManager().reportUnlockAttempt(true /* authenticated */, userId);
}
public void reportPasswordLockout(int timeoutMs, int userId) {
+ if (userId == USER_FRP && frpCredentialEnabled()) {
+ return;
+ }
getTrustManager().reportUnlockLockout(timeoutMs, userId);
}
public int getCurrentFailedPasswordAttempts(int userId) {
+ if (userId == USER_FRP && frpCredentialEnabled()) {
+ return 0;
+ }
return getDevicePolicyManager().getCurrentFailedPasswordAttempts(userId);
}
public int getMaximumFailedPasswordsForWipe(int userId) {
+ if (userId == USER_FRP && frpCredentialEnabled()) {
+ return 0;
+ }
return getDevicePolicyManager().getMaximumFailedPasswordsForWipe(
null /* componentName */, userId);
}
@@ -586,7 +610,7 @@ public class LockPatternUtils {
try{
getLockSettings().setLockCredential(null, CREDENTIAL_TYPE_NONE, savedCredential,
- userHandle);
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userHandle);
} catch (RemoteException e) {
// well, we tried...
}
@@ -651,7 +675,7 @@ public class LockPatternUtils {
setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId);
getLockSettings().setLockCredential(patternToString(pattern), CREDENTIAL_TYPE_PATTERN,
- savedPattern, userId);
+ savedPattern, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId);
// Update the device encryption password.
if (userId == UserHandle.USER_SYSTEM
@@ -765,10 +789,10 @@ public class LockPatternUtils {
* password.
* @param password The password to save
* @param savedPassword The previously saved lock password, or null if none
- * @param quality {@see DevicePolicyManager#getPasswordQuality(android.content.ComponentName)}
+ * @param requestedQuality {@see DevicePolicyManager#getPasswordQuality(android.content.ComponentName)}
* @param userHandle The userId of the user to change the password for
*/
- public void saveLockPassword(String password, String savedPassword, int quality,
+ public void saveLockPassword(String password, String savedPassword, int requestedQuality,
int userHandle) {
try {
if (password == null || password.length() < MIN_LOCK_PASSWORD_SIZE) {
@@ -777,9 +801,9 @@ public class LockPatternUtils {
}
final int computedQuality = PasswordMetrics.computeForPassword(password).quality;
- setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality), userHandle);
+ setLong(PASSWORD_TYPE_KEY, Math.max(requestedQuality, computedQuality), userHandle);
getLockSettings().setLockCredential(password, CREDENTIAL_TYPE_PASSWORD, savedPassword,
- userHandle);
+ requestedQuality, userHandle);
updateEncryptionPasswordIfNeeded(password, computedQuality, userHandle);
updatePasswordHistory(password, userHandle);
@@ -1474,12 +1498,13 @@ public class LockPatternUtils {
}
final int computedQuality = PasswordMetrics.computeForPassword(credential).quality;
+ int quality = Math.max(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC,
+ computedQuality);
if (!getLockSettings().setLockCredentialWithToken(credential, type, tokenHandle,
- token, userId)) {
+ token, quality, userId)) {
return false;
}
- setLong(PASSWORD_TYPE_KEY, Math.max(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC,
- computedQuality), userId);
+ setLong(PASSWORD_TYPE_KEY, quality, userId);
updateEncryptionPasswordIfNeeded(credential, computedQuality, userId);
updatePasswordHistory(credential, userId);
@@ -1488,7 +1513,8 @@ public class LockPatternUtils {
throw new IllegalArgumentException("password must be emtpy for NONE type");
}
if (!getLockSettings().setLockCredentialWithToken(null, CREDENTIAL_TYPE_NONE,
- tokenHandle, token, userId)) {
+ tokenHandle, token, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
+ userId)) {
return false;
}
setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
@@ -1691,4 +1717,12 @@ public class LockPatternUtils {
public boolean isSyntheticPasswordEnabled() {
return getLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM) != 0;
}
+
+ public static boolean userOwnsFrpCredential(UserInfo info) {
+ return info != null && info.isPrimary() && info.isAdmin() && frpCredentialEnabled();
+ }
+
+ public static boolean frpCredentialEnabled() {
+ return FRP_CREDENTIAL_ENABLED;
+ }
}
diff --git a/core/java/com/android/internal/widget/VerifyCredentialResponse.java b/core/java/com/android/internal/widget/VerifyCredentialResponse.java
index 48109ca347a5..ad6020c0846c 100644
--- a/core/java/com/android/internal/widget/VerifyCredentialResponse.java
+++ b/core/java/com/android/internal/widget/VerifyCredentialResponse.java
@@ -18,6 +18,8 @@ package com.android.internal.widget;
import android.os.Parcel;
import android.os.Parcelable;
+import android.service.gatekeeper.GateKeeperResponse;
+import android.util.Slog;
/**
* Response object for a ILockSettings credential verification request.
@@ -32,6 +34,7 @@ public final class VerifyCredentialResponse implements Parcelable {
public static final VerifyCredentialResponse OK = new VerifyCredentialResponse();
public static final VerifyCredentialResponse ERROR
= new VerifyCredentialResponse(RESPONSE_ERROR, 0, null);
+ private static final String TAG = "VerifyCredentialResponse";
private int mResponseCode;
private byte[] mPayload;
@@ -123,4 +126,29 @@ public final class VerifyCredentialResponse implements Parcelable {
private void setPayload(byte[] payload) {
mPayload = payload;
}
+
+ public VerifyCredentialResponse stripPayload() {
+ return new VerifyCredentialResponse(mResponseCode, mTimeout, new byte[0]);
+ }
+
+ public static VerifyCredentialResponse fromGateKeeperResponse(
+ GateKeeperResponse gateKeeperResponse) {
+ VerifyCredentialResponse response;
+ int responseCode = gateKeeperResponse.getResponseCode();
+ if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
+ response = new VerifyCredentialResponse(gateKeeperResponse.getTimeout());
+ } else if (responseCode == GateKeeperResponse.RESPONSE_OK) {
+ byte[] token = gateKeeperResponse.getPayload();
+ if (token == null) {
+ // something's wrong if there's no payload with a challenge
+ Slog.e(TAG, "verifyChallenge response had no associated payload");
+ response = VerifyCredentialResponse.ERROR;
+ } else {
+ response = new VerifyCredentialResponse(token);
+ }
+ } else {
+ response = VerifyCredentialResponse.ERROR;
+ }
+ return response;
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
new file mode 100644
index 000000000000..f73950a648e0
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 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 com.android.internal.widget;
+
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.UserHandle;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class LockPatternUtilsTest {
+
+ @Test
+ public void testUserFrp_isNotRegularUser() throws Exception {
+ assertTrue(LockPatternUtils.USER_FRP < 0);
+ }
+
+ @Test
+ public void testUserFrp_isNotAReservedSpecialUser() throws Exception {
+ assertNotEquals(UserHandle.USER_NULL, LockPatternUtils.USER_FRP);
+ assertNotEquals(UserHandle.USER_ALL, LockPatternUtils.USER_FRP);
+ assertNotEquals(UserHandle.USER_CURRENT, LockPatternUtils.USER_FRP);
+ assertNotEquals(UserHandle.USER_CURRENT_OR_SELF, LockPatternUtils.USER_FRP);
+ }
+}
diff --git a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
new file mode 100644
index 000000000000..80f8e519beb7
--- /dev/null
+++ b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 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 com.android.server;
+
+/**
+ * Internal interface for storing and retrieving persistent data.
+ */
+public interface PersistentDataBlockManagerInternal {
+
+ /** Stores the handle to a lockscreen credential to be used for Factory Reset Protection. */
+ void setFrpCredentialHandle(byte[] handle);
+
+ /** Retrieves handle to a lockscreen credential to be used for Factory Reset Protection. */
+ byte[] getFrpCredentialHandle();
+}
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index e3cd87cc5422..1d4c3db0749c 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -32,6 +32,7 @@ import android.util.Slog;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
import libcore.io.IoUtils;
@@ -71,8 +72,13 @@ public class PersistentDataBlockService extends SystemService {
private static final int HEADER_SIZE = 8;
// Magic number to mark block device as adhering to the format consumed by this service
private static final int PARTITION_TYPE_MARKER = 0x19901873;
+ /** Size of the block reserved for FPR credential, including 4 bytes for the size header. */
+ private static final int FRP_CREDENTIAL_RESERVED_SIZE = 1000;
+ /** Maximum size of the FRP credential handle that can be stored. */
+ private static final int MAX_FRP_CREDENTIAL_HANDLE_SIZE = FRP_CREDENTIAL_RESERVED_SIZE - 4;
// Limit to 100k as blocks larger than this might cause strain on Binder.
private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100;
+
public static final int DIGEST_SIZE_BYTES = 32;
private static final String OEM_UNLOCK_PROP = "sys.oem_unlock_allowed";
private static final String FLASH_LOCK_PROP = "ro.boot.flash.locked";
@@ -136,6 +142,7 @@ public class PersistentDataBlockService extends SystemService {
Thread.currentThread().interrupt();
throw new IllegalStateException("Service " + TAG + " init interrupted", e);
}
+ LocalServices.addService(PersistentDataBlockManagerInternal.class, mInternalService);
}
super.onBootPhase(phase);
}
@@ -382,7 +389,7 @@ public class PersistentDataBlockService extends SystemService {
enforceUid(Binder.getCallingUid());
// Need to ensure we don't write over the last byte
- long maxBlockSize = getBlockDeviceSize() - HEADER_SIZE - 1;
+ long maxBlockSize = getMaximumDataBlockSize();
if (data.length > maxBlockSize) {
// partition is ~500k so shouldn't be a problem to downcast
return (int) -maxBlockSize;
@@ -562,8 +569,99 @@ public class PersistentDataBlockService extends SystemService {
@Override
public long getMaximumDataBlockSize() {
- long actualSize = getBlockDeviceSize() - HEADER_SIZE - 1;
+ long actualSize = getBlockDeviceSize() - HEADER_SIZE - DIGEST_SIZE_BYTES
+ - FRP_CREDENTIAL_RESERVED_SIZE - 1;
return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE;
}
+
+ @Override
+ public boolean hasFrpCredentialHandle() {
+ enforcePersistentDataBlockAccess();
+ return mInternalService.getFrpCredentialHandle() != null;
+ }
+ };
+
+ private PersistentDataBlockManagerInternal mInternalService =
+ new PersistentDataBlockManagerInternal() {
+
+ @Override
+ public void setFrpCredentialHandle(byte[] handle) {
+ Preconditions.checkArgument(handle == null || handle.length > 0,
+ "handle must be null or non-empty");
+ Preconditions.checkArgument(handle == null
+ || handle.length <= MAX_FRP_CREDENTIAL_HANDLE_SIZE,
+ "handle must not be longer than " + MAX_FRP_CREDENTIAL_HANDLE_SIZE);
+
+ FileOutputStream outputStream;
+ try {
+ outputStream = new FileOutputStream(new File(mDataBlockFile));
+ } catch (FileNotFoundException e) {
+ Slog.e(TAG, "partition not available", e);
+ return;
+ }
+
+ ByteBuffer data = ByteBuffer.allocate(FRP_CREDENTIAL_RESERVED_SIZE);
+ data.putInt(handle == null ? 0 : handle.length);
+ if (handle != null) {
+ data.put(handle);
+ }
+ data.flip();
+
+ synchronized (mLock) {
+ if (!mIsWritable) {
+ IoUtils.closeQuietly(outputStream);
+ return;
+ }
+
+ try {
+ FileChannel channel = outputStream.getChannel();
+
+ channel.position(getBlockDeviceSize() - 1 - FRP_CREDENTIAL_RESERVED_SIZE);
+ channel.write(data);
+ outputStream.flush();
+ } catch (IOException e) {
+ Slog.e(TAG, "unable to access persistent partition", e);
+ return;
+ } finally {
+ IoUtils.closeQuietly(outputStream);
+ }
+
+ computeAndWriteDigestLocked();
+ }
+ }
+
+ @Override
+ public byte[] getFrpCredentialHandle() {
+ if (!enforceChecksumValidity()) {
+ return null;
+ }
+
+ DataInputStream inputStream;
+ try {
+ inputStream = new DataInputStream(
+ new FileInputStream(new File(mDataBlockFile)));
+ } catch (FileNotFoundException e) {
+ Slog.e(TAG, "partition not available");
+ return null;
+ }
+
+ try {
+ synchronized (mLock) {
+ inputStream.skip(getBlockDeviceSize() - 1 - FRP_CREDENTIAL_RESERVED_SIZE);
+ int length = inputStream.readInt();
+ if (length <= 0 || length > MAX_FRP_CREDENTIAL_HANDLE_SIZE) {
+ return null;
+ }
+ byte[] bytes = new byte[length];
+ inputStream.readFully(bytes);
+ return bytes;
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "unable to access persistent partition", e);
+ return null;
+ } finally {
+ IoUtils.closeQuietly(inputStream);
+ }
+ }
};
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 270dcbedf368..321b660da1f2 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -19,9 +19,12 @@ package com.android.server.locksettings;
import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE;
import static android.Manifest.permission.READ_CONTACTS;
import static android.content.Context.KEYGUARD_SERVICE;
+
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_ENABLED_KEY;
import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY;
+import static com.android.internal.widget.LockPatternUtils.USER_FRP;
+import static com.android.internal.widget.LockPatternUtils.frpCredentialEnabled;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
@@ -43,7 +46,9 @@ import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.res.Resources;
+import android.database.ContentObserver;
import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -63,7 +68,6 @@ import android.os.storage.StorageManager;
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.provider.Settings.SettingNotFoundException;
-import android.security.GateKeeper;
import android.security.KeyStore;
import android.security.keystore.AndroidKeyStoreProvider;
import android.security.keystore.KeyProperties;
@@ -80,6 +84,7 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.Preconditions;
import com.android.internal.widget.ICheckCredentialProgressCallback;
import com.android.internal.widget.ILockSettings;
import com.android.internal.widget.LockPatternUtils;
@@ -88,6 +93,7 @@ import com.android.server.SystemService;
import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult;
import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken;
+import com.android.server.locksettings.LockSettingsStorage.PersistentData;
import libcore.util.HexEncoding;
@@ -105,7 +111,6 @@ import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -138,6 +143,9 @@ public class LockSettingsService extends ILockSettings.Stub {
// Order of holding lock: mSeparateChallengeLock -> mSpManager -> this
private final Object mSeparateChallengeLock = new Object();
+ private final DeviceProvisionedObserver mDeviceProvisionedObserver =
+ new DeviceProvisionedObserver();
+
private final Injector mInjector;
private final Context mContext;
private final Handler mHandler;
@@ -266,13 +274,13 @@ public class LockSettingsService extends ILockSettings.Stub {
try {
randomLockSeed = SecureRandom.getInstance("SHA1PRNG").generateSeed(40);
String newPassword = String.valueOf(HexEncoding.encode(randomLockSeed));
+ final int quality = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
setLockCredentialInternal(newPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
- managedUserPassword, managedUserId);
+ managedUserPassword, quality, managedUserId);
// We store a private credential for the managed user that's unlocked by the primary
// account holder's credential. As such, the user will never be prompted to enter this
// password directly, so we always store a password.
- setLong(LockPatternUtils.PASSWORD_TYPE_KEY,
- DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, managedUserId);
+ setLong(LockPatternUtils.PASSWORD_TYPE_KEY, quality, managedUserId);
tieProfileLockToParent(managedUserId, newPassword);
} catch (NoSuchAlgorithmException | RemoteException e) {
Slog.e(TAG, "Fail to tie managed profile", e);
@@ -353,7 +361,7 @@ public class LockSettingsService extends ILockSettings.Stub {
}
public SyntheticPasswordManager getSyntheticPasswordManager(LockSettingsStorage storage) {
- return new SyntheticPasswordManager(storage);
+ return new SyntheticPasswordManager(storage, getUserManager());
}
public int binderGetCallingUid() {
@@ -547,6 +555,7 @@ public class LockSettingsService extends ILockSettings.Stub {
} catch (RemoteException e) {
Slog.e(TAG, "Failure retrieving IGateKeeperService", e);
}
+ mDeviceProvisionedObserver.onSystemReady();
// TODO: maybe skip this for split system user mode.
mStorage.prefetchUser(UserHandle.USER_SYSTEM);
}
@@ -783,6 +792,8 @@ public class LockSettingsService extends ILockSettings.Stub {
}
private void setStringUnchecked(String key, int userId, String value) {
+ Preconditions.checkArgument(userId != USER_FRP, "cannot store lock settings for FRP user");
+
mStorage.writeKeyValue(key, value, userId);
if (ArrayUtils.contains(SETTINGS_TO_BACKUP, key)) {
BackupManager.dataChanged("com.android.providers.settings");
@@ -820,6 +831,10 @@ public class LockSettingsService extends ILockSettings.Stub {
}
}
+ if (userId == USER_FRP) {
+ return getFrpStringUnchecked(key);
+ }
+
if (LockPatternUtils.LEGACY_LOCK_PATTERN_ENABLED.equals(key)) {
key = Settings.Secure.LOCK_PATTERN_ENABLED;
}
@@ -827,6 +842,17 @@ public class LockSettingsService extends ILockSettings.Stub {
return mStorage.readKeyValue(key, defaultValue, userId);
}
+ private String getFrpStringUnchecked(String key) {
+ if (LockPatternUtils.PASSWORD_TYPE_KEY.equals(key)) {
+ return String.valueOf(readFrpPasswordQuality());
+ }
+ return null;
+ }
+
+ private int readFrpPasswordQuality() {
+ return mStorage.readPersistentDataBlock().qualityForUi;
+ }
+
@Override
public boolean havePassword(int userId) throws RemoteException {
synchronized (mSpManager) {
@@ -1036,12 +1062,13 @@ public class LockSettingsService extends ILockSettings.Stub {
// credential, otherwise they get lost
if (profilePasswordMap != null && profilePasswordMap.containsKey(managedUserId)) {
setLockCredentialInternal(null, LockPatternUtils.CREDENTIAL_TYPE_NONE,
- profilePasswordMap.get(managedUserId), managedUserId);
+ profilePasswordMap.get(managedUserId),
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, managedUserId);
} else {
Slog.wtf(TAG, "clear tied profile challenges, but no password supplied.");
// Supplying null here would lead to untrusted credential change
setLockCredentialInternal(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, null,
- managedUserId);
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, managedUserId);
}
mStorage.removeChildProfileLock(managedUserId);
removeKeystoreProfileKey(managedUserId);
@@ -1063,18 +1090,19 @@ public class LockSettingsService extends ILockSettings.Stub {
// This method should be called by LockPatternUtil only, all internal methods in this class
// should call setLockCredentialInternal.
@Override
- public void setLockCredential(String credential, int type, String savedCredential, int userId)
+ public void setLockCredential(String credential, int type, String savedCredential,
+ int requestedQuality, int userId)
throws RemoteException {
checkWritePermission(userId);
synchronized (mSeparateChallengeLock) {
- setLockCredentialInternal(credential, type, savedCredential, userId);
+ setLockCredentialInternal(credential, type, savedCredential, requestedQuality, userId);
setSeparateProfileChallengeEnabled(userId, true, null);
notifyPasswordChanged(userId);
}
}
private void setLockCredentialInternal(String credential, int credentialType,
- String savedCredential, int userId) throws RemoteException {
+ String savedCredential, int requestedQuality, int userId) throws RemoteException {
// Normalize savedCredential and credential such that empty string is always represented
// as null.
if (TextUtils.isEmpty(savedCredential)) {
@@ -1086,10 +1114,11 @@ public class LockSettingsService extends ILockSettings.Stub {
synchronized (mSpManager) {
if (isSyntheticPasswordBasedCredentialLocked(userId)) {
spBasedSetLockCredentialInternalLocked(credential, credentialType, savedCredential,
- userId);
+ requestedQuality, userId);
return;
}
}
+
if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
if (credential != null) {
Slog.wtf(TAG, "CredentialType is none, but credential is non-null.");
@@ -1101,6 +1130,12 @@ public class LockSettingsService extends ILockSettings.Stub {
fixateNewestUserKeyAuth(userId);
synchronizeUnifiedWorkChallengeForProfiles(userId, null);
notifyActivePasswordMetricsAvailable(null, userId);
+
+ if (mStorage.getPersistentDataBlock() != null
+ && LockPatternUtils.userOwnsFrpCredential(mUserManager.getUserInfo(userId))) {
+ // If owner, write to persistent storage for FRP
+ mStorage.writePersistentDataBlock(PersistentData.TYPE_NONE, userId, 0, null);
+ }
return;
}
if (credential == null) {
@@ -1133,9 +1168,9 @@ public class LockSettingsService extends ILockSettings.Stub {
synchronized (mSpManager) {
if (shouldMigrateToSyntheticPasswordLocked(userId)) {
initializeSyntheticPasswordLocked(currentHandle.hash, savedCredential,
- currentHandle.type, userId);
+ currentHandle.type, requestedQuality, userId);
spBasedSetLockCredentialInternalLocked(credential, credentialType, savedCredential,
- userId);
+ requestedQuality, userId);
return;
}
}
@@ -1153,6 +1188,12 @@ public class LockSettingsService extends ILockSettings.Stub {
// Refresh the auth token
doVerifyCredential(credential, credentialType, true, 0, userId, null /* progressCallback */);
synchronizeUnifiedWorkChallengeForProfiles(userId, null);
+ if (mStorage.getPersistentDataBlock() != null
+ && LockPatternUtils.userOwnsFrpCredential(mUserManager.getUserInfo(userId))) {
+ // If owner, write to persistent storage for FRP
+ mStorage.writePersistentDataBlock(PersistentData.TYPE_GATEKEEPER, userId,
+ requestedQuality, willStore.toBytes());
+ }
} else {
throw new RemoteException("Failed to enroll " +
(credentialType == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD ? "password"
@@ -1161,23 +1202,7 @@ public class LockSettingsService extends ILockSettings.Stub {
}
private VerifyCredentialResponse convertResponse(GateKeeperResponse gateKeeperResponse) {
- VerifyCredentialResponse response;
- int responseCode = gateKeeperResponse.getResponseCode();
- if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
- response = new VerifyCredentialResponse(gateKeeperResponse.getTimeout());
- } else if (responseCode == GateKeeperResponse.RESPONSE_OK) {
- byte[] token = gateKeeperResponse.getPayload();
- if (token == null) {
- // something's wrong if there's no payload with a challenge
- Slog.e(TAG, "verifyChallenge response had no associated payload");
- response = VerifyCredentialResponse.ERROR;
- } else {
- response = new VerifyCredentialResponse(token);
- }
- } else {
- response = VerifyCredentialResponse.ERROR;
- }
- return response;
+ return VerifyCredentialResponse.fromGateKeeperResponse(gateKeeperResponse);
}
@VisibleForTesting
@@ -1403,6 +1428,11 @@ public class LockSettingsService extends ILockSettings.Stub {
if (TextUtils.isEmpty(credential)) {
throw new IllegalArgumentException("Credential can't be null or empty");
}
+ if (userId == USER_FRP && Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 0) != 0) {
+ Slog.e(TAG, "FRP credential can only be verified prior to provisioning.");
+ return VerifyCredentialResponse.ERROR;
+ }
synchronized (mSpManager) {
if (isSyntheticPasswordBasedCredentialLocked(userId)) {
VerifyCredentialResponse response = spBasedDoVerifyCredentialLocked(credential,
@@ -1413,7 +1443,18 @@ public class LockSettingsService extends ILockSettings.Stub {
return response;
}
}
- CredentialHash storedHash = mStorage.readCredentialHash(userId);
+ final CredentialHash storedHash;
+ if (userId == USER_FRP) {
+ PersistentData data = mStorage.readPersistentDataBlock();
+ if (data.type != PersistentData.TYPE_GATEKEEPER) {
+ Slog.wtf(TAG, "Expected PersistentData.TYPE_GATEKEEPER, but was: " + data.type);
+ return VerifyCredentialResponse.ERROR;
+ }
+ return verifyFrpCredential(credential, credentialType, data, progressCallback);
+ } else {
+ storedHash = mStorage.readCredentialHash(userId);
+ }
+
if (storedHash.type != credentialType) {
Slog.wtf(TAG, "doVerifyCredential type mismatch with stored credential??"
+ " stored: " + storedHash.type + " passed in: " + credentialType);
@@ -1436,13 +1477,37 @@ public class LockSettingsService extends ILockSettings.Stub {
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
mStrongAuth.reportSuccessfulStrongAuthUnlock(userId);
if (shouldReEnrollBaseZero) {
- setLockCredentialInternal(credential, storedHash.type, credentialToVerify, userId);
+ setLockCredentialInternal(credential, storedHash.type, credentialToVerify,
+ DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId);
}
}
return response;
}
+ private VerifyCredentialResponse verifyFrpCredential(String credential, int credentialType,
+ PersistentData data, ICheckCredentialProgressCallback progressCallback)
+ throws RemoteException {
+ CredentialHash storedHash = CredentialHash.fromBytes(data.payload);
+ if (storedHash.type != credentialType) {
+ Slog.wtf(TAG, "doVerifyCredential type mismatch with stored credential??"
+ + " stored: " + storedHash.type + " passed in: " + credentialType);
+ return VerifyCredentialResponse.ERROR;
+ }
+ if (ArrayUtils.isEmpty(storedHash.hash) || TextUtils.isEmpty(credential)) {
+ Slog.e(TAG, "Stored hash or credential is empty");
+ return VerifyCredentialResponse.ERROR;
+ }
+ VerifyCredentialResponse response = VerifyCredentialResponse.fromGateKeeperResponse(
+ getGateKeeperService().verifyChallenge(data.userId, 0 /* challenge */,
+ storedHash.hash, credential.getBytes()));
+ if (progressCallback != null
+ && response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
+ progressCallback.onCredentialVerified();
+ }
+ return response;
+ }
+
@Override
public VerifyCredentialResponse verifyTiedProfileChallenge(String credential, int type,
long challenge, int userId) throws RemoteException {
@@ -1521,7 +1586,11 @@ public class LockSettingsService extends ILockSettings.Stub {
unlockUser(userId, fakeToken, fakeToken);
// migrate credential to GateKeeper
- setLockCredentialInternal(credential, storedHash.type, null, userId);
+ setLockCredentialInternal(credential, storedHash.type, null,
+ storedHash.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN
+ ? DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
+ : DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
+ /* TODO(roosa): keep the same password quality */, userId);
if (!hasChallenge) {
notifyActivePasswordMetricsAvailable(credential, userId);
return VerifyCredentialResponse.OK;
@@ -1557,15 +1626,21 @@ public class LockSettingsService extends ILockSettings.Stub {
(TrustManager) mContext.getSystemService(Context.TRUST_SERVICE);
trustManager.setDeviceLockedForUser(userId, false);
}
+ int reEnrollQuality = storedHash.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN
+ ? DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
+ : DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
+ /* TODO(roosa): keep the same password quality */;
if (shouldReEnroll) {
- setLockCredentialInternal(credential, storedHash.type, credential, userId);
+ setLockCredentialInternal(credential, storedHash.type, credential,
+ reEnrollQuality, userId);
} else {
// Now that we've cleared of all required GK migration, let's do the final
// migration to synthetic password.
synchronized (mSpManager) {
if (shouldMigrateToSyntheticPasswordLocked(userId)) {
AuthenticationToken auth = initializeSyntheticPasswordLocked(
- storedHash.hash, credential, storedHash.type, userId);
+ storedHash.hash, credential, storedHash.type, reEnrollQuality,
+ userId);
activateEscrowTokens(auth, userId);
}
}
@@ -1857,7 +1932,8 @@ public class LockSettingsService extends ILockSettings.Stub {
* FOR THE FIRST TIME on a SP-enabled device. New credential and new SID will be created
*/
private AuthenticationToken initializeSyntheticPasswordLocked(byte[] credentialHash,
- String credential, int credentialType, int userId) throws RemoteException {
+ String credential, int credentialType, int requestedQuality,
+ int userId) throws RemoteException {
Slog.i(TAG, "Initialize SyntheticPassword for user: " + userId);
AuthenticationToken auth = mSpManager.newSyntheticPasswordAndSid(getGateKeeperService(),
credentialHash, credential, userId);
@@ -1866,7 +1942,7 @@ public class LockSettingsService extends ILockSettings.Stub {
return null;
}
long handle = mSpManager.createPasswordBasedSyntheticPassword(getGateKeeperService(),
- credential, credentialType, auth, userId);
+ credential, credentialType, auth, requestedQuality, userId);
if (credential != null) {
if (credentialHash == null) {
// Since when initializing SP, we didn't provide an existing password handle
@@ -1895,6 +1971,10 @@ public class LockSettingsService extends ILockSettings.Stub {
}
private boolean isSyntheticPasswordBasedCredentialLocked(int userId) throws RemoteException {
+ if (userId == USER_FRP) {
+ final int type = mStorage.readPersistentDataBlock().type;
+ return type == PersistentData.TYPE_SP || type == PersistentData.TYPE_SP_WEAVER;
+ }
long handle = getSyntheticPasswordHandleLocked(userId);
// This is a global setting
long enabled = getLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM);
@@ -1919,6 +1999,11 @@ public class LockSettingsService extends ILockSettings.Stub {
if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
userCredential = null;
}
+ if (userId == USER_FRP) {
+ return mSpManager.verifyFrpCredential(getGateKeeperService(),
+ userCredential, credentialType, progressCallback);
+ }
+
long handle = getSyntheticPasswordHandleLocked(userId);
AuthenticationResult authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
getGateKeeperService(), handle, userCredential, userId);
@@ -1969,10 +2054,10 @@ public class LockSettingsService extends ILockSettings.Stub {
* added back when new password is set in future.
*/
private long setLockCredentialWithAuthTokenLocked(String credential, int credentialType,
- AuthenticationToken auth, int userId) throws RemoteException {
+ AuthenticationToken auth, int requestedQuality, int userId) throws RemoteException {
if (DEBUG) Slog.d(TAG, "setLockCredentialWithAuthTokenLocked: user=" + userId);
long newHandle = mSpManager.createPasswordBasedSyntheticPassword(getGateKeeperService(),
- credential, credentialType, auth, userId);
+ credential, credentialType, auth, requestedQuality, userId);
final Map<Integer, String> profilePasswords;
if (credential != null) {
// // not needed by synchronizeUnifiedWorkChallengeForProfiles()
@@ -2014,7 +2099,7 @@ public class LockSettingsService extends ILockSettings.Stub {
}
private void spBasedSetLockCredentialInternalLocked(String credential, int credentialType,
- String savedCredential, int userId) throws RemoteException {
+ String savedCredential, int requestedQuality, int userId) throws RemoteException {
if (DEBUG) Slog.d(TAG, "spBasedSetLockCredentialInternalLocked: user=" + userId);
if (isManagedProfileWithUnifiedLock(userId)) {
// get credential from keystore when managed profile has unified lock
@@ -2037,7 +2122,8 @@ public class LockSettingsService extends ILockSettings.Stub {
if (auth != null) {
// We are performing a trusted credential change i.e. a correct existing credential
// is provided
- setLockCredentialWithAuthTokenLocked(credential, credentialType, auth, userId);
+ setLockCredentialWithAuthTokenLocked(credential, credentialType, auth, requestedQuality,
+ userId);
mSpManager.destroyPasswordBasedSyntheticPassword(handle, userId);
} else if (response != null
&& response.getResponseCode() == VerifyCredentialResponse.RESPONSE_ERROR){
@@ -2046,7 +2132,8 @@ public class LockSettingsService extends ILockSettings.Stub {
// Still support this for now but this flow will be removed in the next release.
Slog.w(TAG, "Untrusted credential change invoked");
- initializeSyntheticPasswordLocked(null, credential, credentialType, userId);
+ initializeSyntheticPasswordLocked(null, credential, credentialType, requestedQuality,
+ userId);
synchronizeUnifiedWorkChallengeForProfiles(userId, null);
mSpManager.destroyPasswordBasedSyntheticPassword(handle, userId);
} else /* response == null || responseCode == VerifyCredentialResponse.RESPONSE_RETRY */ {
@@ -2070,7 +2157,8 @@ public class LockSettingsService extends ILockSettings.Stub {
if (!isUserSecure(userId)) {
if (shouldMigrateToSyntheticPasswordLocked(userId)) {
auth = initializeSyntheticPasswordLocked(null, null,
- LockPatternUtils.CREDENTIAL_TYPE_NONE, userId);
+ LockPatternUtils.CREDENTIAL_TYPE_NONE,
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userId);
} else /* isSyntheticPasswordBasedCredentialLocked(userId) */ {
long pwdHandle = getSyntheticPasswordHandleLocked(userId);
auth = mSpManager.unwrapPasswordBasedSyntheticPassword(getGateKeeperService(),
@@ -2132,7 +2220,7 @@ public class LockSettingsService extends ILockSettings.Stub {
@Override
public boolean setLockCredentialWithToken(String credential, int type, long tokenHandle,
- byte[] token, int userId) throws RemoteException {
+ byte[] token, int requestedQuality, int userId) throws RemoteException {
ensureCallerSystemUid();
boolean result;
synchronized (mSpManager) {
@@ -2140,7 +2228,7 @@ public class LockSettingsService extends ILockSettings.Stub {
throw new SecurityException("Escrow token is disabled on the current user");
}
result = setLockCredentialWithTokenInternal(credential, type, tokenHandle, token,
- userId);
+ requestedQuality, userId);
}
if (result) {
synchronized (mSeparateChallengeLock) {
@@ -2152,7 +2240,7 @@ public class LockSettingsService extends ILockSettings.Stub {
}
private boolean setLockCredentialWithTokenInternal(String credential, int type,
- long tokenHandle, byte[] token, int userId) throws RemoteException {
+ long tokenHandle, byte[] token, int requestedQuality, int userId) throws RemoteException {
synchronized (mSpManager) {
AuthenticationResult result = mSpManager.unwrapTokenBasedSyntheticPassword(
getGateKeeperService(), tokenHandle, token, userId);
@@ -2161,7 +2249,8 @@ public class LockSettingsService extends ILockSettings.Stub {
return false;
}
long oldHandle = getSyntheticPasswordHandleLocked(userId);
- setLockCredentialWithAuthTokenLocked(credential, type, result.authToken, userId);
+ setLockCredentialWithAuthTokenLocked(credential, type, result.authToken,
+ requestedQuality, userId);
mSpManager.destroyPasswordBasedSyntheticPassword(oldHandle, userId);
return true;
}
@@ -2261,4 +2350,69 @@ public class LockSettingsService extends ILockSettings.Stub {
throw new SecurityException("Only system can call this API.");
}
}
+
+ private class DeviceProvisionedObserver extends ContentObserver {
+ private final Uri mDeviceProvisionedUri = Settings.Global.getUriFor(
+ Settings.Global.DEVICE_PROVISIONED);
+
+ private boolean mRegistered;
+
+ public DeviceProvisionedObserver() {
+ super(null);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (mDeviceProvisionedUri.equals(uri)) {
+ updateRegistration();
+
+ if (isProvisioned()) {
+ Slog.i(TAG, "Reporting device setup complete to IGateKeeperService");
+ reportDeviceSetupComplete();
+ }
+ }
+ }
+
+ public void onSystemReady() {
+ if (frpCredentialEnabled()) {
+ updateRegistration();
+ } else {
+ // If we don't intend to use frpCredentials and we're not provisioned yet, send
+ // deviceSetupComplete immediately, so gatekeeper can discard any lingering
+ // credentials immediately.
+ if (!isProvisioned()) {
+ Slog.i(TAG, "FRP credential disabled, reporting device setup complete "
+ + "to Gatekeeper immediately");
+ reportDeviceSetupComplete();
+ }
+ }
+ }
+
+ private void reportDeviceSetupComplete() {
+ try {
+ getGateKeeperService().reportDeviceSetupComplete();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failure reporting to IGateKeeperService", e);
+ }
+ }
+
+ private void updateRegistration() {
+ boolean register = !isProvisioned();
+ if (register == mRegistered) {
+ return;
+ }
+ if (register) {
+ mContext.getContentResolver().registerContentObserver(mDeviceProvisionedUri,
+ false, this);
+ } else {
+ mContext.getContentResolver().unregisterContentObserver(this);
+ }
+ mRegistered = register;
+ }
+
+ private boolean isProvisioned() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index d621a6824eac..79372e483af5 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -18,6 +18,8 @@ package com.android.server.locksettings;
import static android.content.Context.USER_SERVICE;
+import android.annotation.Nullable;
+import android.app.admin.DevicePolicyManager;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.UserInfo;
@@ -25,6 +27,7 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.Environment;
+import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager;
import android.util.ArrayMap;
@@ -33,8 +36,15 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils;
+import com.android.server.LocalServices;
+import com.android.server.PersistentDataBlockManagerInternal;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
@@ -79,12 +89,18 @@ class LockSettingsStorage {
private final Cache mCache = new Cache();
private final Object mFileWriteLock = new Object();
+ private PersistentDataBlockManagerInternal mPersistentDataBlockManagerInternal;
+
@VisibleForTesting
public static class CredentialHash {
static final int VERSION_LEGACY = 0;
static final int VERSION_GATEKEEPER = 1;
private CredentialHash(byte[] hash, int type, int version) {
+ this(hash, type, version, false /* isBaseZeroPattern */);
+ }
+
+ private CredentialHash(byte[] hash, int type, int version, boolean isBaseZeroPattern) {
if (type != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
if (hash == null) {
throw new RuntimeException("Empty hash for CredentialHash");
@@ -97,14 +113,12 @@ class LockSettingsStorage {
this.hash = hash;
this.type = type;
this.version = version;
- this.isBaseZeroPattern = false;
+ this.isBaseZeroPattern = isBaseZeroPattern;
}
- private CredentialHash(byte[] hash, boolean isBaseZeroPattern) {
- this.hash = hash;
- this.type = LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
- this.version = VERSION_GATEKEEPER;
- this.isBaseZeroPattern = isBaseZeroPattern;
+ private static CredentialHash createBaseZeroPattern(byte[] hash) {
+ return new CredentialHash(hash, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
+ VERSION_GATEKEEPER, true /* isBaseZeroPattern */);
}
static CredentialHash create(byte[] hash, int type) {
@@ -123,6 +137,44 @@ class LockSettingsStorage {
int type;
int version;
boolean isBaseZeroPattern;
+
+ public byte[] toBytes() {
+ Preconditions.checkState(!isBaseZeroPattern, "base zero patterns are not serializable");
+
+ try {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(os);
+ dos.write(version);
+ dos.write(type);
+ if (hash != null && hash.length > 0) {
+ dos.writeInt(hash.length);
+ dos.write(hash);
+ } else {
+ dos.writeInt(0);
+ }
+ dos.close();
+ return os.toByteArray();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static CredentialHash fromBytes(byte[] bytes) {
+ try {
+ DataInputStream is = new DataInputStream(new ByteArrayInputStream(bytes));
+ int version = is.read();
+ int type = is.read();
+ int hashSize = is.readInt();
+ byte[] hash = null;
+ if (hashSize > 0) {
+ hash = new byte[hashSize];
+ is.readFully(hash);
+ }
+ return new CredentialHash(hash, type, version);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
public LockSettingsStorage(Context context) {
@@ -234,7 +286,7 @@ class LockSettingsStorage {
stored = readFile(getBaseZeroLockPatternFilename(userId));
if (!ArrayUtils.isEmpty(stored)) {
- return new CredentialHash(stored, true);
+ return CredentialHash.createBaseZeroPattern(stored);
}
stored = readFile(getLegacyLockPatternFilename(userId));
@@ -551,6 +603,108 @@ class LockSettingsStorage {
mCache.clear();
}
+ @Nullable
+ public PersistentDataBlockManagerInternal getPersistentDataBlock() {
+ if (mPersistentDataBlockManagerInternal == null) {
+ mPersistentDataBlockManagerInternal =
+ LocalServices.getService(PersistentDataBlockManagerInternal.class);
+ }
+ return mPersistentDataBlockManagerInternal;
+ }
+
+ public void writePersistentDataBlock(int persistentType, int userId, int qualityForUi,
+ byte[] payload) {
+ PersistentDataBlockManagerInternal persistentDataBlock = getPersistentDataBlock();
+ if (persistentDataBlock == null) {
+ return;
+ }
+ persistentDataBlock.setFrpCredentialHandle(PersistentData.toBytes(
+ persistentType, userId, qualityForUi, payload));
+ }
+
+ public PersistentData readPersistentDataBlock() {
+ PersistentDataBlockManagerInternal persistentDataBlock = getPersistentDataBlock();
+ if (persistentDataBlock == null) {
+ return PersistentData.NONE;
+ }
+ return PersistentData.fromBytes(persistentDataBlock.getFrpCredentialHandle());
+ }
+
+ public static class PersistentData {
+ static final byte VERSION_1 = 1;
+ static final int VERSION_1_HEADER_SIZE = 1 + 1 + 4 + 4;
+
+ public static final int TYPE_NONE = 0;
+ public static final int TYPE_GATEKEEPER = 1;
+ public static final int TYPE_SP = 2;
+ public static final int TYPE_SP_WEAVER = 3;
+
+ public static final PersistentData NONE = new PersistentData(TYPE_NONE,
+ UserHandle.USER_NULL, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, null);
+
+ final int type;
+ final int userId;
+ final int qualityForUi;
+ final byte[] payload;
+
+ private PersistentData(int type, int userId, int qualityForUi, byte[] payload) {
+ this.type = type;
+ this.userId = userId;
+ this.qualityForUi = qualityForUi;
+ this.payload = payload;
+ }
+
+ public static PersistentData fromBytes(byte[] frpData) {
+ if (frpData == null || frpData.length == 0) {
+ return NONE;
+ }
+
+ DataInputStream is = new DataInputStream(new ByteArrayInputStream(frpData));
+ try {
+ byte version = is.readByte();
+ if (version == PersistentData.VERSION_1) {
+ int type = is.readByte() & 0xFF;
+ int userId = is.readInt();
+ int qualityForUi = is.readInt();
+ byte[] payload = new byte[frpData.length - VERSION_1_HEADER_SIZE];
+ System.arraycopy(frpData, VERSION_1_HEADER_SIZE, payload, 0, payload.length);
+ return new PersistentData(type, userId, qualityForUi, payload);
+ } else {
+ Slog.wtf(TAG, "Unknown PersistentData version code: " + version);
+ return null;
+ }
+ } catch (IOException e) {
+ Slog.wtf(TAG, "Could not parse PersistentData", e);
+ return null;
+ }
+ }
+
+ public static byte[] toBytes(int persistentType, int userId, int qualityForUi,
+ byte[] payload) {
+ if (persistentType == PersistentData.TYPE_NONE) {
+ Preconditions.checkArgument(payload == null,
+ "TYPE_NONE must have empty payload");
+ return null;
+ }
+ Preconditions.checkArgument(payload != null && payload.length > 0,
+ "empty payload must only be used with TYPE_NONE");
+
+ ByteArrayOutputStream os = new ByteArrayOutputStream(
+ VERSION_1_HEADER_SIZE + payload.length);
+ DataOutputStream dos = new DataOutputStream(os);
+ try {
+ dos.writeByte(PersistentData.VERSION_1);
+ dos.writeByte(persistentType);
+ dos.writeInt(userId);
+ dos.writeInt(qualityForUi);
+ dos.write(payload);
+ } catch (IOException e) {
+ throw new RuntimeException("ByteArrayOutputStream cannot throw IOException");
+ }
+ return os.toByteArray();
+ }
+ }
+
public interface Callback {
void initialize(SQLiteDatabase db);
}
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 2f8a1b449d84..f45c208db919 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -18,13 +18,15 @@ package com.android.server.locksettings;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.admin.DevicePolicyManager;
import android.hardware.weaver.V1_0.IWeaver;
import android.hardware.weaver.V1_0.WeaverConfig;
import android.hardware.weaver.V1_0.WeaverReadResponse;
import android.hardware.weaver.V1_0.WeaverReadStatus;
import android.hardware.weaver.V1_0.WeaverStatus;
-import android.os.RemoteException;
import android.security.GateKeeper;
+import android.os.RemoteException;
+import android.os.UserManager;
import android.service.gatekeeper.GateKeeperResponse;
import android.service.gatekeeper.IGateKeeperService;
import android.util.ArrayMap;
@@ -33,8 +35,10 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.widget.ICheckCredentialProgressCallback;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.VerifyCredentialResponse;
+import com.android.server.locksettings.LockSettingsStorage.PersistentData;
import libcore.util.HexEncoding;
@@ -253,8 +257,11 @@ public class SyntheticPasswordManager {
private IWeaver mWeaver;
private WeaverConfig mWeaverConfig;
- public SyntheticPasswordManager(LockSettingsStorage storage) {
+ private final UserManager mUserManager;
+
+ public SyntheticPasswordManager(LockSettingsStorage storage, UserManager userManager) {
mStorage = storage;
+ mUserManager = userManager;
}
@VisibleForTesting
@@ -557,7 +564,8 @@ public class SyntheticPasswordManager {
* @see #clearSidForUser
*/
public long createPasswordBasedSyntheticPassword(IGateKeeperService gatekeeper,
- String credential, int credentialType, AuthenticationToken authToken, int userId)
+ String credential, int credentialType, AuthenticationToken authToken,
+ int requestedQuality, int userId)
throws RemoteException {
if (credential == null || credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
credentialType = LockPatternUtils.CREDENTIAL_TYPE_NONE;
@@ -579,6 +587,7 @@ public class SyntheticPasswordManager {
return DEFAULT_HANDLE;
}
saveWeaverSlot(weaverSlot, handle, userId);
+ synchronizeWeaverFrpPassword(pwd, requestedQuality, userId, weaverSlot);
pwd.passwordHandle = null;
sid = GateKeeper.INVALID_SECURE_USER_ID;
@@ -598,6 +607,7 @@ public class SyntheticPasswordManager {
sid = sidFromPasswordHandle(pwd.passwordHandle);
applicationId = transformUnderSecdiscardable(pwdToken,
createSecdiscardable(handle, userId));
+ synchronizeFrpPassword(pwd, requestedQuality, userId);
}
saveState(PASSWORD_DATA_NAME, pwd.toBytes(), handle, userId);
@@ -606,6 +616,57 @@ public class SyntheticPasswordManager {
return handle;
}
+ public VerifyCredentialResponse verifyFrpCredential(IGateKeeperService gatekeeper,
+ String userCredential, int credentialType,
+ ICheckCredentialProgressCallback progressCallback) throws RemoteException {
+ PersistentData persistentData = mStorage.readPersistentDataBlock();
+ if (persistentData.type == PersistentData.TYPE_SP) {
+ PasswordData pwd = PasswordData.fromBytes(persistentData.payload);
+ byte[] pwdToken = computePasswordToken(userCredential, pwd);
+
+ GateKeeperResponse response = gatekeeper.verify(fakeUid(persistentData.userId),
+ pwd.passwordHandle, passwordTokenToGkInput(pwdToken));
+ return VerifyCredentialResponse.fromGateKeeperResponse(response);
+ } else if (persistentData.type == PersistentData.TYPE_SP_WEAVER) {
+ PasswordData pwd = PasswordData.fromBytes(persistentData.payload);
+ byte[] pwdToken = computePasswordToken(userCredential, pwd);
+ int weaverSlot = persistentData.userId;
+
+ return weaverVerify(weaverSlot, passwordTokenToWeaverKey(pwdToken)).stripPayload();
+ } else {
+ Log.e(TAG, "persistentData.type must be TYPE_SP or TYPE_SP_WEAVER, but is "
+ + persistentData.type);
+ return VerifyCredentialResponse.ERROR;
+ }
+ }
+
+
+ private void synchronizeFrpPassword(PasswordData pwd,
+ int requestedQuality, int userId) {
+ if (mStorage.getPersistentDataBlock() != null
+ && LockPatternUtils.userOwnsFrpCredential(mUserManager.getUserInfo(userId))) {
+ if (pwd.passwordType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
+ mStorage.writePersistentDataBlock(PersistentData.TYPE_SP, userId, requestedQuality,
+ pwd.toBytes());
+ } else {
+ mStorage.writePersistentDataBlock(PersistentData.TYPE_NONE, userId, 0, null);
+ }
+ }
+ }
+
+ private void synchronizeWeaverFrpPassword(PasswordData pwd, int requestedQuality, int userId,
+ int weaverSlot) {
+ if (mStorage.getPersistentDataBlock() != null
+ && LockPatternUtils.userOwnsFrpCredential(mUserManager.getUserInfo(userId))) {
+ if (pwd.passwordType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
+ mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_WEAVER, weaverSlot,
+ requestedQuality, pwd.toBytes());
+ } else {
+ mStorage.writePersistentDataBlock(PersistentData.TYPE_NONE, 0, 0, null);
+ }
+ }
+ }
+
private ArrayMap<Integer, ArrayMap<Long, TokenData>> tokenMap = new ArrayMap<>();
public long createTokenBasedSyntheticPassword(byte[] token, int userId) {
@@ -730,6 +791,12 @@ public class SyntheticPasswordManager {
if (reenrollResponse.getResponseCode() == GateKeeperResponse.RESPONSE_OK) {
pwd.passwordHandle = reenrollResponse.getPayload();
saveState(PASSWORD_DATA_NAME, pwd.toBytes(), handle, userId);
+ synchronizeFrpPassword(pwd,
+ pwd.passwordType == LockPatternUtils.CREDENTIAL_TYPE_PATTERN
+ ? DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
+ : DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
+ /* TODO(roosa): keep the same password quality */,
+ userId);
} else {
Log.w(TAG, "Fail to re-enroll user password for user " + userId);
// continue the flow anyway
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index 7de46d93a737..84cca0eb050b 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -96,7 +96,7 @@ public class BaseLockSettingsServiceTests extends AndroidTestCase {
storageDir.mkdirs();
}
- mSpManager = new MockSyntheticPasswordManager(mStorage, mGateKeeperService);
+ mSpManager = new MockSyntheticPasswordManager(mStorage, mGateKeeperService, mUserManager);
mService = new LockSettingsServiceTestable(mContext, mLockPatternUtils,
mStorage, mGateKeeperService, mKeyStore, mStorageManager, mActivityManager,
mSpManager);
@@ -164,4 +164,3 @@ public class BaseLockSettingsServiceTests extends AndroidTestCase {
assertFalse(Arrays.equals(expected, actual));
}
}
-
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index 2f0ac3811795..cb3249293928 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -16,6 +16,11 @@
package com.android.server.locksettings;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
@@ -44,21 +49,23 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
}
public void testCreatePasswordPrimaryUser() throws RemoteException {
- testCreateCredential(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD);
+ testCreateCredential(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD,
+ PASSWORD_QUALITY_ALPHABETIC);
}
public void testCreatePatternPrimaryUser() throws RemoteException {
- testCreateCredential(PRIMARY_USER_ID, "123456789", CREDENTIAL_TYPE_PATTERN);
+ testCreateCredential(PRIMARY_USER_ID, "123456789", CREDENTIAL_TYPE_PATTERN,
+ PASSWORD_QUALITY_SOMETHING);
}
public void testChangePasswordPrimaryUser() throws RemoteException {
testChangeCredentials(PRIMARY_USER_ID, "78963214", CREDENTIAL_TYPE_PATTERN,
- "asdfghjk", CREDENTIAL_TYPE_PASSWORD);
+ "asdfghjk", CREDENTIAL_TYPE_PASSWORD, PASSWORD_QUALITY_ALPHABETIC);
}
public void testChangePatternPrimaryUser() throws RemoteException {
testChangeCredentials(PRIMARY_USER_ID, "!£$%^&*(())", CREDENTIAL_TYPE_PASSWORD,
- "1596321", CREDENTIAL_TYPE_PATTERN);
+ "1596321", CREDENTIAL_TYPE_PATTERN, PASSWORD_QUALITY_SOMETHING);
}
public void testChangePasswordFailPrimaryUser() throws RemoteException {
@@ -68,13 +75,14 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
try {
mService.setLockCredential("newpwd", CREDENTIAL_TYPE_PASSWORD, "badpwd",
- PRIMARY_USER_ID);
+ PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
fail("Did not fail when enrolling using incorrect credential");
} catch (RemoteException expected) {
assertTrue(expected.getMessage().equals(FAILED_MESSAGE));
}
try {
- mService.setLockCredential("newpwd", CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+ mService.setLockCredential("newpwd", CREDENTIAL_TYPE_PASSWORD, null,
+ PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID);
fail("Did not fail when enrolling using incorrect credential");
} catch (RemoteException expected) {
assertTrue(expected.getMessage().equals(FAILED_MESSAGE));
@@ -85,7 +93,8 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
public void testClearPasswordPrimaryUser() throws RemoteException {
final String PASSWORD = "password";
initializeStorageWithCredential(PRIMARY_USER_ID, PASSWORD, CREDENTIAL_TYPE_PASSWORD, 1234);
- mService.setLockCredential(null, CREDENTIAL_TYPE_NONE, PASSWORD, PRIMARY_USER_ID);
+ mService.setLockCredential(null, CREDENTIAL_TYPE_NONE, PASSWORD,
+ PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID);
assertFalse(mService.havePassword(PRIMARY_USER_ID));
assertFalse(mService.havePattern(PRIMARY_USER_ID));
assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
@@ -94,7 +103,7 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
public void testManagedProfileUnifiedChallenge() throws RemoteException {
final String UnifiedPassword = "testManagedProfileUnifiedChallenge-pwd";
mService.setLockCredential(UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
- PRIMARY_USER_ID);
+ PASSWORD_QUALITY_COMPLEX, PRIMARY_USER_ID);
mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
@@ -129,14 +138,14 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
mStorageManager.setIgnoreBadUnlock(true);
// Change primary password and verify that profile SID remains
mService.setLockCredential("pwd", LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
- UnifiedPassword, PRIMARY_USER_ID);
+ UnifiedPassword, PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
mStorageManager.setIgnoreBadUnlock(false);
assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
assertNull(mGateKeeperService.getAuthToken(TURNED_OFF_PROFILE_USER_ID));
// Clear unified challenge
mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, UnifiedPassword,
- PRIMARY_USER_ID);
+ PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID);
assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
assertEquals(0, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
assertEquals(0, mGateKeeperService.getSecureUserId(TURNED_OFF_PROFILE_USER_ID));
@@ -146,14 +155,14 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
final String primaryPassword = "testManagedProfileSeparateChallenge-primary";
final String profilePassword = "testManagedProfileSeparateChallenge-profile";
mService.setLockCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
- PRIMARY_USER_ID);
+ PASSWORD_QUALITY_COMPLEX, PRIMARY_USER_ID);
/* Currently in LockSettingsService.setLockCredential, unlockUser() is called with the new
* credential as part of verifyCredential() before the new credential is committed in
* StorageManager. So we relax the check in our mock StorageManager to allow that.
*/
mStorageManager.setIgnoreBadUnlock(true);
mService.setLockCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
- MANAGED_PROFILE_USER_ID);
+ PASSWORD_QUALITY_COMPLEX, MANAGED_PROFILE_USER_ID);
mStorageManager.setIgnoreBadUnlock(false);
final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
@@ -180,7 +189,7 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
// Change primary credential and make sure we don't affect profile
mStorageManager.setIgnoreBadUnlock(true);
mService.setLockCredential("pwd", LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
- primaryPassword, PRIMARY_USER_ID);
+ primaryPassword, PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
mStorageManager.setIgnoreBadUnlock(false);
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
@@ -188,17 +197,17 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
}
- private void testCreateCredential(int userId, String credential, int type)
+ private void testCreateCredential(int userId, String credential, int type, int quality)
throws RemoteException {
- mService.setLockCredential(credential, type, null, userId);
+ mService.setLockCredential(credential, type, null, quality, userId);
assertVerifyCredentials(userId, credential, type, -1);
}
private void testChangeCredentials(int userId, String newCredential, int newType,
- String oldCredential, int oldType) throws RemoteException {
+ String oldCredential, int oldType, int quality) throws RemoteException {
final long sid = 1234;
initializeStorageWithCredential(userId, oldCredential, oldType, sid);
- mService.setLockCredential(newCredential, newType, oldCredential, userId);
+ mService.setLockCredential(newCredential, newType, oldCredential, quality, userId);
assertVerifyCredentials(userId, newCredential, newType, sid);
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
index 449a54c082dc..a0578c9098dd 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -22,8 +22,6 @@ import static org.mockito.Mockito.when;
import android.app.NotificationManager;
import android.app.admin.DevicePolicyManager;
-import android.content.Context;
-import android.content.ContextWrapper;
import android.content.pm.UserInfo;
import android.database.sqlite.SQLiteDatabase;
import android.os.FileUtils;
@@ -33,6 +31,8 @@ import android.test.AndroidTestCase;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
+import com.android.server.locksettings.LockSettingsStorage.PersistentData;
+
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
@@ -43,11 +43,14 @@ import java.util.concurrent.CountDownLatch;
* runtest frameworks-services -c com.android.server.locksettings.LockSettingsStorageTests
*/
public class LockSettingsStorageTests extends AndroidTestCase {
+ private static final int SOME_USER_ID = 1034;
private final byte[] PASSWORD_0 = "thepassword0".getBytes();
private final byte[] PASSWORD_1 = "password1".getBytes();
private final byte[] PATTERN_0 = "123654".getBytes();
private final byte[] PATTERN_1 = "147852369".getBytes();
+ public static final byte[] PAYLOAD = new byte[] {1, 2, -1, -2, 33};
+
LockSettingsStorage mStorage;
File mStorageDir;
@@ -342,6 +345,83 @@ public class LockSettingsStorageTests extends AndroidTestCase {
assertEquals(null, mStorage.readSyntheticPasswordState(10, 1234L, "state"));
}
+ public void testPersistentData_serializeUnserialize() {
+ byte[] serialized = PersistentData.toBytes(PersistentData.TYPE_GATEKEEPER, SOME_USER_ID,
+ DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, PAYLOAD);
+ PersistentData deserialized = PersistentData.fromBytes(serialized);
+
+ assertEquals(PersistentData.TYPE_GATEKEEPER, deserialized.type);
+ assertEquals(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, deserialized.qualityForUi);
+ assertArrayEquals(PAYLOAD, deserialized.payload);
+ }
+
+ public void testPersistentData_unserializeNull() {
+ PersistentData deserialized = PersistentData.fromBytes(null);
+ assertSame(PersistentData.NONE, deserialized);
+ }
+
+ public void testPersistentData_unserializeEmptyArray() {
+ PersistentData deserialized = PersistentData.fromBytes(new byte[0]);
+ assertSame(PersistentData.NONE, deserialized);
+ }
+
+ public void testPersistentData_unserialize_version1() {
+ // This test ensures that we can read serialized VERSION_1 PersistentData even if we change
+ // the wire format in the future.
+ byte[] serializedVersion1 = new byte[] {
+ 1, /* PersistentData.VERSION_1 */
+ 2, /* PersistentData.TYPE_SP */
+ 0x00, 0x00, 0x04, 0x0A, /* SOME_USER_ID */
+ 0x00, 0x03, 0x00, 0x00, /* PASSWORD_NUMERIC_COMPLEX */
+ 1, 2, -1, -2, 33, /* PAYLOAD */
+ };
+ PersistentData deserialized = PersistentData.fromBytes(serializedVersion1);
+ assertEquals(PersistentData.TYPE_SP, deserialized.type);
+ assertEquals(SOME_USER_ID, deserialized.userId);
+ assertEquals(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX,
+ deserialized.qualityForUi);
+ assertArrayEquals(PAYLOAD, deserialized.payload);
+
+ // Make sure the constants we use on the wire do not change.
+ assertEquals(0, PersistentData.TYPE_NONE);
+ assertEquals(1, PersistentData.TYPE_GATEKEEPER);
+ assertEquals(2, PersistentData.TYPE_SP);
+ assertEquals(3, PersistentData.TYPE_SP_WEAVER);
+ }
+
+ public void testCredentialHash_serializeUnserialize() {
+ byte[] serialized = CredentialHash.create(
+ PAYLOAD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD).toBytes();
+ CredentialHash deserialized = CredentialHash.fromBytes(serialized);
+
+ assertEquals(CredentialHash.VERSION_GATEKEEPER, deserialized.version);
+ assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, deserialized.type);
+ assertArrayEquals(PAYLOAD, deserialized.hash);
+ assertFalse(deserialized.isBaseZeroPattern);
+ }
+
+ public void testCredentialHash_unserialize_versionGatekeeper() {
+ // This test ensures that we can read serialized VERSION_GATEKEEPER CredentialHashes
+ // even if we change the wire format in the future.
+ byte[] serialized = new byte[] {
+ 1, /* VERSION_GATEKEEPER */
+ 2, /* CREDENTIAL_TYPE_PASSWORD */
+ 0, 0, 0, 5, /* hash length */
+ 1, 2, -1, -2, 33, /* hash */
+ };
+ CredentialHash deserialized = CredentialHash.fromBytes(serialized);
+
+ assertEquals(CredentialHash.VERSION_GATEKEEPER, deserialized.version);
+ assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, deserialized.type);
+ assertArrayEquals(PAYLOAD, deserialized.hash);
+ assertFalse(deserialized.isBaseZeroPattern);
+
+ // Make sure the constants we use on the wire do not change.
+ assertEquals(-1, LockPatternUtils.CREDENTIAL_TYPE_NONE);
+ assertEquals(1, LockPatternUtils.CREDENTIAL_TYPE_PATTERN);
+ assertEquals(2, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD);
+ }
+
private static void assertArrayEquals(byte[] expected, byte[] actual) {
if (!Arrays.equals(expected, actual)) {
fail("expected:<" + Arrays.toString(expected) +
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/MockGateKeeperService.java b/services/tests/servicestests/src/com/android/server/locksettings/MockGateKeeperService.java
index eefd36194485..b89c1d1d5811 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/MockGateKeeperService.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/MockGateKeeperService.java
@@ -173,6 +173,10 @@ public class MockGateKeeperService implements IGateKeeperService {
}
@Override
+ public void reportDeviceSetupComplete() throws RemoteException {
+ }
+
+ @Override
public long getSecureUserId(int userId) throws RemoteException {
if (sidMap.containsKey(userId)) {
return sidMap.get(userId);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java b/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
index ddef5dcfa0f6..d7468c2c02c8 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
@@ -17,6 +17,7 @@ package com.android.server.locksettings;
import android.hardware.weaver.V1_0.IWeaver;
import android.os.RemoteException;
+import android.os.UserManager;
import android.util.ArrayMap;
import junit.framework.AssertionFailedError;
@@ -35,8 +36,8 @@ public class MockSyntheticPasswordManager extends SyntheticPasswordManager {
private IWeaver mWeaverService;
public MockSyntheticPasswordManager(LockSettingsStorage storage,
- MockGateKeeperService gatekeeper) {
- super(storage);
+ MockGateKeeperService gatekeeper, UserManager userManager) {
+ super(storage, userManager);
mGateKeeper = gatekeeper;
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 0d35385c577c..ba4ff33893f2 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -16,9 +16,15 @@
package com.android.server.locksettings;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_ENABLED_KEY;
import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY;
+import android.app.admin.DevicePolicyManager;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -26,6 +32,7 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.VerifyCredentialResponse;
import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult;
import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken;
+import com.android.server.locksettings.SyntheticPasswordManager.PasswordData;
/**
@@ -33,6 +40,9 @@ import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationTo
*/
public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
+ public static final byte[] PAYLOAD = new byte[] {1, 2, -1, -2, 55};
+ public static final byte[] PAYLOAD2 = new byte[] {2, 3, -2, -3, 44, 1};
+
@Override
protected void setUp() throws Exception {
super.setUp();
@@ -47,11 +57,13 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
final int USER_ID = 10;
final String PASSWORD = "user-password";
final String BADPASSWORD = "bad-password";
- MockSyntheticPasswordManager manager = new MockSyntheticPasswordManager(mStorage, mGateKeeperService);
+ MockSyntheticPasswordManager manager = new MockSyntheticPasswordManager(mStorage,
+ mGateKeeperService, mUserManager);
AuthenticationToken authToken = manager.newSyntheticPasswordAndSid(mGateKeeperService, null,
null, USER_ID);
long handle = manager.createPasswordBasedSyntheticPassword(mGateKeeperService, PASSWORD,
- LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, authToken, USER_ID);
+ LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, authToken, PASSWORD_QUALITY_ALPHABETIC,
+ USER_ID);
AuthenticationResult result = manager.unwrapPasswordBasedSyntheticPassword(mGateKeeperService, handle, PASSWORD, USER_ID);
assertEquals(result.authToken.deriveKeyStorePassword(), authToken.deriveKeyStorePassword());
@@ -76,7 +88,8 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
final String PASSWORD = "testPasswordMigration-password";
disableSyntheticPassword(PRIMARY_USER_ID);
- mService.setLockCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+ mService.setLockCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+ PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
final byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
enableSyntheticPassword(PRIMARY_USER_ID);
@@ -94,7 +107,11 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
private void initializeCredentialUnderSP(String password, int userId) throws RemoteException {
enableSyntheticPassword(userId);
- mService.setLockCredential(password, password != null ? LockPatternUtils.CREDENTIAL_TYPE_PASSWORD : LockPatternUtils.CREDENTIAL_TYPE_NONE, null, userId);
+ int quality = password != null ? PASSWORD_QUALITY_ALPHABETIC
+ : PASSWORD_QUALITY_UNSPECIFIED;
+ int type = password != null ? LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
+ : LockPatternUtils.CREDENTIAL_TYPE_NONE;
+ mService.setLockCredential(password, type, null, quality, userId);
}
public void testSyntheticPasswordChangeCredential() throws RemoteException {
@@ -103,7 +120,8 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
- mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, PASSWORD, PRIMARY_USER_ID);
+ mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, PASSWORD,
+ PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
mGateKeeperService.clearSecureUserId(PRIMARY_USER_ID);
assertEquals(VerifyCredentialResponse.RESPONSE_OK,
mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
@@ -129,11 +147,13 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
// clear password
- mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, PASSWORD, PRIMARY_USER_ID);
+ mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, PASSWORD,
+ PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID);
assertEquals(0 ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
// set a new password
- mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+ mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+ PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
assertEquals(VerifyCredentialResponse.RESPONSE_OK,
mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
assertNotSame(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
@@ -146,11 +166,13 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
// clear password
- mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+ mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, null,
+ PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID);
assertEquals(0 ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
// set a new password
- mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+ mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+ PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
assertEquals(VerifyCredentialResponse.RESPONSE_OK,
mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
assertNotSame(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
@@ -163,7 +185,8 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
// Untrusted change password
- mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+ mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+ PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
assertNotSame(0 ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
assertNotSame(sid ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
@@ -177,7 +200,8 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
final String UnifiedPassword = "testManagedProfileUnifiedChallengeMigration-pwd";
disableSyntheticPassword(PRIMARY_USER_ID);
disableSyntheticPassword(MANAGED_PROFILE_USER_ID);
- mService.setLockCredential(UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+ mService.setLockCredential(UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+ PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
@@ -207,8 +231,10 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
public void testManagedProfileSeparateChallengeMigration() throws RemoteException {
final String primaryPassword = "testManagedProfileSeparateChallengeMigration-primary";
final String profilePassword = "testManagedProfileSeparateChallengeMigration-profile";
- mService.setLockCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
- mService.setLockCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, MANAGED_PROFILE_USER_ID);
+ mService.setLockCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+ PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+ mService.setLockCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+ PASSWORD_QUALITY_ALPHABETIC, MANAGED_PROFILE_USER_ID);
final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
final byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
@@ -251,7 +277,8 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode();
assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
- mService.setLockCredentialWithToken(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, handle, TOKEN.getBytes(), PRIMARY_USER_ID);
+ mService.setLockCredentialWithToken(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
+ handle, TOKEN.getBytes(), PASSWORD_QUALITY_SOMETHING, PRIMARY_USER_ID);
assertEquals(VerifyCredentialResponse.RESPONSE_OK,
mService.verifyCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0, PRIMARY_USER_ID).getResponseCode());
@@ -271,8 +298,10 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode();
assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
- mService.setLockCredentialWithToken(null, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, TOKEN.getBytes(), PRIMARY_USER_ID);
- mService.setLockCredentialWithToken(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, TOKEN.getBytes(), PRIMARY_USER_ID);
+ mService.setLockCredentialWithToken(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, handle,
+ TOKEN.getBytes(), PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID);
+ mService.setLockCredentialWithToken(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
+ handle, TOKEN.getBytes(), PASSWORD_QUALITY_SOMETHING, PRIMARY_USER_ID);
assertEquals(VerifyCredentialResponse.RESPONSE_OK,
mService.verifyCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0, PRIMARY_USER_ID).getResponseCode());
@@ -293,9 +322,11 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode();
assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
- mService.setLockCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, PASSWORD, PRIMARY_USER_ID);
+ mService.setLockCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, PASSWORD,
+ PASSWORD_QUALITY_SOMETHING, PRIMARY_USER_ID);
- mService.setLockCredentialWithToken(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, TOKEN.getBytes(), PRIMARY_USER_ID);
+ mService.setLockCredentialWithToken(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+ handle, TOKEN.getBytes(), PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
assertEquals(VerifyCredentialResponse.RESPONSE_OK,
mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
@@ -326,7 +357,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
// Set up pre-SP user password
disableSyntheticPassword(PRIMARY_USER_ID);
mService.setLockCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
- PRIMARY_USER_ID);
+ PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
enableSyntheticPassword(PRIMARY_USER_ID);
long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID);
@@ -340,10 +371,51 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
}
+ public void testPasswordData_serializeDeserialize() {
+ PasswordData data = new PasswordData();
+ data.scryptN = 11;
+ data.scryptR = 22;
+ data.scryptP = 33;
+ data.passwordType = CREDENTIAL_TYPE_PASSWORD;
+ data.salt = PAYLOAD;
+ data.passwordHandle = PAYLOAD2;
+
+ PasswordData deserialized = PasswordData.fromBytes(data.toBytes());
+
+ assertEquals(11, deserialized.scryptN);
+ assertEquals(22, deserialized.scryptR);
+ assertEquals(33, deserialized.scryptP);
+ assertEquals(CREDENTIAL_TYPE_PASSWORD, deserialized.passwordType);
+ assertArrayEquals(PAYLOAD, deserialized.salt);
+ assertArrayEquals(PAYLOAD2, deserialized.passwordHandle);
+ }
+
+ public void testPasswordData_deserialize() {
+ // Test that we can deserialize existing PasswordData and don't inadvertently change the
+ // wire format.
+ byte[] serialized = new byte[] {
+ 0, 0, 0, 2, /* CREDENTIAL_TYPE_PASSWORD */
+ 11, /* scryptN */
+ 22, /* scryptR */
+ 33, /* scryptP */
+ 0, 0, 0, 5, /* salt.length */
+ 1, 2, -1, -2, 55, /* salt */
+ 0, 0, 0, 6, /* passwordHandle.length */
+ 2, 3, -2, -3, 44, 1, /* passwordHandle */
+ };
+ PasswordData deserialized = PasswordData.fromBytes(serialized);
+
+ assertEquals(11, deserialized.scryptN);
+ assertEquals(22, deserialized.scryptR);
+ assertEquals(33, deserialized.scryptP);
+ assertEquals(CREDENTIAL_TYPE_PASSWORD, deserialized.passwordType);
+ assertArrayEquals(PAYLOAD, deserialized.salt);
+ assertArrayEquals(PAYLOAD2, deserialized.passwordHandle);
+ }
+
// b/34600579
//TODO: add non-migration work profile case, and unify/un-unify transition.
//TODO: test token after user resets password
//TODO: test token based reset after unified work challenge
//TODO: test clear password after unified work challenge
}
-