Credential FRP: Add implementation
- Adds a facility to store a credential handle that survives factory reset
- Adds a method to KeyguardManager for verifying the stored credential for SetupWizard
- Dark launches persisting the primary user's credential as the FRP credential (behind a default-off flag)
Future work:
- Use a separate GK handle / synthetic password for the FRP credential
- Enroll the FRP credential in verifyCredential for the upgrade case
Bug: 36814845
Test: runtest -x core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java && runtest -x services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java && runtest -x services/tests/servicestests/src/com/android/server/SyntheticPasswordTests.java
Change-Id: Ia739408c5ecb169e5f09670cd9ceaa7febc2b1cc
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 0000000..80f8e51
--- /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 e3cd87c..1d4c3db 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -32,6 +32,7 @@
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 @@
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 @@
Thread.currentThread().interrupt();
throw new IllegalStateException("Service " + TAG + " init interrupted", e);
}
+ LocalServices.addService(PersistentDataBlockManagerInternal.class, mInternalService);
}
super.onBootPhase(phase);
}
@@ -382,7 +389,7 @@
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 @@
@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 270dcbe..321b660 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 @@
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.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.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.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.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.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 @@
// 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 @@
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 SyntheticPasswordManager getSyntheticPasswordManager(LockSettingsStorage storage) {
- return new SyntheticPasswordManager(storage);
+ return new SyntheticPasswordManager(storage, getUserManager());
}
public int binderGetCallingUid() {
@@ -547,6 +555,7 @@
} 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 @@
}
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 @@
}
}
+ 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 @@
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 @@
// 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 @@
// 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 @@
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 @@
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 @@
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 @@
// 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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
(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 @@
* 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 @@
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 @@
}
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 @@
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 @@
* 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 @@
}
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 @@
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 @@
// 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 @@
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 @@
@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 @@
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 @@
}
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 @@
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 @@
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 d621a68..79372e48 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 @@
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.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 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 @@
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 @@
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 @@
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 @@
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 @@
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 2f8a1b4..f45c208 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 @@
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 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 @@
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 @@
* @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 @@
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 @@
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 @@
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 @@
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 7de46d9..84cca0e 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 @@
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 @@
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 2f0ac38..cb32492 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 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 @@
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 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 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 @@
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 @@
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 @@
// 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 @@
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 449a54c..a0578c9 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 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 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 @@
* 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 @@
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 eefd361..b89c1d1 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 @@
}
@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 ddef5dc..d7468c2 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 @@
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 @@
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 0d35385..ba4ff33 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.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 @@
*/
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 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 @@
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 @@
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 @@
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 @@
// 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 @@
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
}
-