diff options
| author | 2017-05-31 04:33:33 +0000 | |
|---|---|---|
| committer | 2017-05-31 04:33:39 +0000 | |
| commit | 390ab841e25106e74c39240858972d3ecb8de652 (patch) | |
| tree | 301dae16c192a97f63b3e841dd601716dc1c8cfa | |
| parent | 9ff881ad72694e77e13978340aa7c4b5d6d41741 (diff) | |
| parent | 7374d3a4bca6bfbf7da1ef5dbf0db9f35f0c8315 (diff) | |
Merge "Credential FRP: Add implementation"
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 } - |