diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index e0b631ed85f19..0e1407fc272bf 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2304,7 +2304,7 @@ public class DevicePolicyManager { * Determine whether the current password the user has set is sufficient to meet the policy * requirements (e.g. quality, minimum length) that have been requested by the admins of this * user and its participating profiles. Restrictions on profiles that have a separate challenge - * are not taken into account. + * are not taken into account. The user must be unlocked in order to perform the check. *
* The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has @@ -2317,6 +2317,7 @@ public class DevicePolicyManager { * @return Returns true if the password meets the current requirements, else false. * @throws SecurityException if the calling application does not own an active administrator * that uses {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} + * @throws InvalidStateException if the user is not unlocked. */ public boolean isActivePasswordSufficient() { if (mService != null) { @@ -3824,6 +3825,19 @@ public class DevicePolicyManager { } } + /** + * @hide + */ + public void reportPasswordChanged(@UserIdInt int userId) { + if (mService != null) { + try { + mService.reportPasswordChanged(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + /** * @hide */ diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index afb426c876ecb..9be694e8a319c 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -123,6 +123,7 @@ interface IDevicePolicyManager { boolean hasGrantedPolicy(in ComponentName policyReceiver, int usesPolicy, int userHandle); void setActivePasswordState(in PasswordMetrics metrics, int userHandle); + void reportPasswordChanged(int userId); void reportFailedPasswordAttempt(int userHandle); void reportSuccessfulPasswordAttempt(int userHandle); void reportFailedFingerprintAttempt(int userHandle); diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 63b700bde76b4..2a8077cf3f701 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -590,8 +590,6 @@ public class LockPatternUtils { setCredentialRequiredToDecrypt(false); } - getDevicePolicyManager().setActivePasswordState(new PasswordMetrics(), userHandle); - onAfterChangingPassword(userHandle); } @@ -644,6 +642,7 @@ public class LockPatternUtils { + MIN_LOCK_PATTERN_SIZE + " dots long."); } + setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId); getLockSettings().setLockPattern(patternToString(pattern), savedPattern, userId); DevicePolicyManager dpm = getDevicePolicyManager(); @@ -659,10 +658,6 @@ public class LockPatternUtils { } setBoolean(PATTERN_EVER_CHOSEN_KEY, true, userId); - - setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId); - dpm.setActivePasswordState(new PasswordMetrics( - DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, pattern.size()), userId); onAfterChangingPassword(userId); } catch (RemoteException re) { Log.e(TAG, "Couldn't save lock pattern " + re); @@ -775,10 +770,9 @@ public class LockPatternUtils { + "of length " + MIN_LOCK_PASSWORD_SIZE); } + final int computedQuality = PasswordMetrics.computeForPassword(password).quality; + setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality), userHandle); getLockSettings().setLockPassword(password, savedPassword, userHandle); - getLockSettings().setSeparateProfileChallengeEnabled(userHandle, true, null); - final PasswordMetrics metrics = PasswordMetrics.computeForPassword(password); - final int computedQuality = metrics.quality; // Update the device encryption password. if (userHandle == UserHandle.USER_SYSTEM @@ -796,15 +790,6 @@ public class LockPatternUtils { } } - setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality), userHandle); - if (computedQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) { - metrics.quality = Math.max(quality, metrics.quality); - dpm.setActivePasswordState(metrics, userHandle); - } else { - // The password is not anything. - dpm.setActivePasswordState(new PasswordMetrics(), userHandle); - } - // Add the password to the password history. We assume all // password hashes have the same length for simplicity of implementation. String passwordHistory = getString(PASSWORD_HISTORY_KEY, userHandle); diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java index 13985304bd877..4d6ffe6e99c6f 100644 --- a/services/core/java/com/android/server/LockSettingsService.java +++ b/services/core/java/com/android/server/LockSettingsService.java @@ -16,12 +16,14 @@ package com.android.server; +import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.KeyguardManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.admin.DevicePolicyManager; +import android.app.admin.PasswordMetrics; import android.app.backup.BackupManager; import android.app.trust.IStrongAuthTracker; import android.app.trust.TrustManager; @@ -931,6 +933,7 @@ public class LockSettingsService extends ILockSettings.Stub { synchronized (mSeparateChallengeLock) { setLockPatternInternal(pattern, savedCredential, userId); setSeparateProfileChallengeEnabled(userId, true, null); + notifyPasswordChanged(userId); } } @@ -945,6 +948,7 @@ public class LockSettingsService extends ILockSettings.Stub { setKeystorePassword(null, userId); fixateNewestUserKeyAuth(userId); onUserLockChanged(userId); + notifyActivePasswordMetricsAvailable(null, userId); return; } @@ -994,6 +998,7 @@ public class LockSettingsService extends ILockSettings.Stub { synchronized (mSeparateChallengeLock) { setLockPasswordInternal(password, savedCredential, userId); setSeparateProfileChallengeEnabled(userId, true, null); + notifyPasswordChanged(userId); } } @@ -1007,6 +1012,7 @@ public class LockSettingsService extends ILockSettings.Stub { setKeystorePassword(null, userId); fixateNewestUserKeyAuth(userId); onUserLockChanged(userId); + notifyActivePasswordMetricsAvailable(null, userId); return; } @@ -1420,6 +1426,7 @@ public class LockSettingsService extends ILockSettings.Stub { // migrate credential to GateKeeper credentialUtil.setCredential(credential, null, userId); if (!hasChallenge) { + notifyActivePasswordMetricsAvailable(credential, userId); return VerifyCredentialResponse.OK; } // Fall through to get the auth token. Technically this should never happen, @@ -1459,6 +1466,7 @@ public class LockSettingsService extends ILockSettings.Stub { if (progressCallback != null) { progressCallback.onCredentialVerified(); } + notifyActivePasswordMetricsAvailable(credential, userId); unlockKeystore(credential, userId); Slog.i(TAG, "Unlocking user " + userId + @@ -1482,6 +1490,36 @@ public class LockSettingsService extends ILockSettings.Stub { return response; } + private void notifyActivePasswordMetricsAvailable(String password, @UserIdInt int userId) { + final PasswordMetrics metrics; + if (password == null) { + metrics = new PasswordMetrics(); + } else { + metrics = PasswordMetrics.computeForPassword(password); + metrics.quality = mLockPatternUtils.getKeyguardStoredPasswordQuality(userId); + } + + // Asynchronous to avoid dead lock + mHandler.post(() -> { + DevicePolicyManager dpm = (DevicePolicyManager) + mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); + dpm.setActivePasswordState(metrics, userId); + }); + } + + /** + * Call after {@link #notifyActivePasswordMetricsAvailable} so metrics are updated before + * reporting the password changed. + */ + private void notifyPasswordChanged(@UserIdInt int userId) { + // Same handler as notifyActivePasswordMetricsAvailable to ensure correct ordering + mHandler.post(() -> { + DevicePolicyManager dpm = (DevicePolicyManager) + mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); + dpm.reportPasswordChanged(userId); + }); + } + @Override public boolean checkVoldPassword(int userId) throws RemoteException { if (!mFirstCallToVold) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 8f46414353a8d..4d3a35e0fa12b 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -253,7 +253,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final String ATTR_DEVICE_PROVISIONING_CONFIG_APPLIED = "device-provisioning-config-applied"; private static final String ATTR_DEVICE_PAIRED = "device-paired"; - private static final String ATTR_DELEGATED_CERT_INSTALLER = "delegated-cert-installer"; private static final String ATTR_APPLICATION_RESTRICTIONS_MANAGER = "application-restrictions-manager"; @@ -2342,20 +2341,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.endTag(null, "failed-password-attempts"); } - final PasswordMetrics metrics = policy.mActivePasswordMetrics; - if (!metrics.isDefault()) { - out.startTag(null, "active-password"); - out.attribute(null, "quality", Integer.toString(metrics.quality)); - out.attribute(null, "length", Integer.toString(metrics.length)); - out.attribute(null, "uppercase", Integer.toString(metrics.upperCase)); - out.attribute(null, "lowercase", Integer.toString(metrics.lowerCase)); - out.attribute(null, "letters", Integer.toString(metrics.letters)); - out.attribute(null, "numeric", Integer.toString(metrics.numeric)); - out.attribute(null, "symbols", Integer.toString(metrics.symbols)); - out.attribute(null, "nonletter", Integer.toString(metrics.nonLetter)); - out.endTag(null, "active-password"); - } - for (int i = 0; i < policy.mAcceptedCaCertificates.size(); i++) { out.startTag(null, TAG_ACCEPTED_CA_CERTIFICATES); out.attribute(null, ATTR_NAME, policy.mAcceptedCaCertificates.valueAt(i)); @@ -2456,6 +2441,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { JournaledFile journal = makeJournaledFile(userHandle); FileInputStream stream = null; File file = journal.chooseForRead(); + boolean needsRewrite = false; try { stream = new FileInputStream(file); XmlPullParser parser = Xml.newPullParser(); @@ -2542,16 +2528,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } else if ("password-owner".equals(tag)) { policy.mPasswordOwner = Integer.parseInt( parser.getAttributeValue(null, "value")); - } else if ("active-password".equals(tag)) { - final PasswordMetrics m = policy.mActivePasswordMetrics; - m.quality = Integer.parseInt(parser.getAttributeValue(null, "quality")); - m.length = Integer.parseInt(parser.getAttributeValue(null, "length")); - m.upperCase = Integer.parseInt(parser.getAttributeValue(null, "uppercase")); - m.lowerCase = Integer.parseInt(parser.getAttributeValue(null, "lowercase")); - m.letters = Integer.parseInt(parser.getAttributeValue(null, "letters")); - m.numeric = Integer.parseInt(parser.getAttributeValue(null, "numeric")); - m.symbols = Integer.parseInt(parser.getAttributeValue(null, "symbols")); - m.nonLetter = Integer.parseInt(parser.getAttributeValue(null, "nonletter")); } else if (TAG_ACCEPTED_CA_CERTIFICATES.equals(tag)) { policy.mAcceptedCaCertificates.add(parser.getAttributeValue(null, ATTR_NAME)); } else if (TAG_LOCK_TASK_COMPONENTS.equals(tag)) { @@ -2577,6 +2553,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { policy.mAdminBroadcastPending = Boolean.toString(true).equals(pending); } else if (TAG_INITIALIZATION_BUNDLE.equals(tag)) { policy.mInitBundle = PersistableBundle.restoreFromXml(parser); + } else if ("active-password".equals(tag)) { + needsRewrite = true; } else { Slog.w(LOG_TAG, "Unknown tag: " + tag); XmlUtils.skipCurrentTag(parser); @@ -2596,27 +2574,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Ignore } + // Might need to upgrade the file by rewriting it + if (needsRewrite) { + saveSettingsLocked(userHandle); + } + // Generate a list of admins from the admin map policy.mAdminList.addAll(policy.mAdminMap.values()); - // Validate that what we stored for the password quality matches - // sufficiently what is currently set. Note that this is only - // a sanity check in case the two get out of sync; this should - // never normally happen. - final long identity = mInjector.binderClearCallingIdentity(); - try { - int actualPasswordQuality = mLockPatternUtils.getActivePasswordQuality(userHandle); - if (actualPasswordQuality < policy.mActivePasswordMetrics.quality) { - Slog.w(LOG_TAG, "Active password quality 0x" - + Integer.toHexString(policy.mActivePasswordMetrics.quality) - + " does not match actual quality 0x" - + Integer.toHexString(actualPasswordQuality)); - policy.mActivePasswordMetrics = new PasswordMetrics(); - } - } finally { - mInjector.binderRestoreCallingIdentity(identity); - } - validatePasswordOwnerLocked(policy); updateMaximumTimeToLockLocked(userHandle); updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle); @@ -3850,6 +3815,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private boolean isActivePasswordSufficientForUserLocked( DevicePolicyData policy, int userHandle, boolean parent) { + enforceUserUnlocked(userHandle, parent); + final int requiredPasswordQuality = getPasswordQuality(null, userHandle, parent); if (policy.mActivePasswordMetrics.quality < requiredPasswordQuality) { return false; @@ -4924,33 +4891,52 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return; } enforceFullCrossUsersPermission(userHandle); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BIND_DEVICE_ADMIN, null); + + // If the managed profile doesn't have a separate password, set the metrics to default + if (isManagedProfile(userHandle) && !isSeparateProfileChallengeEnabled(userHandle)) { + metrics = new PasswordMetrics(); + } + + validateQualityConstant(metrics.quality); + DevicePolicyData policy = getUserData(userHandle); + synchronized (this) { + policy.mActivePasswordMetrics = metrics; + } + } + + @Override + public void reportPasswordChanged(@UserIdInt int userId) { + if (!mHasFeature) { + return; + } + enforceFullCrossUsersPermission(userId); // Managed Profile password can only be changed when it has a separate challenge. - if (!isSeparateProfileChallengeEnabled(userHandle)) { - enforceNotManagedProfile(userHandle, "set the active password"); + if (!isSeparateProfileChallengeEnabled(userId)) { + enforceNotManagedProfile(userId, "set the active password"); } mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BIND_DEVICE_ADMIN, null); - validateQualityConstant(metrics.quality); - DevicePolicyData policy = getUserData(userHandle); + DevicePolicyData policy = getUserData(userId); long ident = mInjector.binderClearCallingIdentity(); try { synchronized (this) { - policy.mActivePasswordMetrics = metrics; policy.mFailedPasswordAttempts = 0; - saveSettingsLocked(userHandle); - updatePasswordExpirationsLocked(userHandle); - setExpirationAlarmCheckLocked(mContext, userHandle, /* parent */ false); + saveSettingsLocked(userId); + updatePasswordExpirationsLocked(userId); + setExpirationAlarmCheckLocked(mContext, userId, /* parent */ false); // Send a broadcast to each profile using this password as its primary unlock. sendAdminCommandForLockscreenPoliciesLocked( DeviceAdminReceiver.ACTION_PASSWORD_CHANGED, - DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, userHandle); + DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, userId); } - removeCaApprovalsIfNeeded(userHandle); + removeCaApprovalsIfNeeded(userId); } finally { mInjector.binderRestoreCallingIdentity(ident); } @@ -6598,6 +6584,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { "User must be running and unlocked"); } + private void enforceUserUnlocked(@UserIdInt int userId, boolean parent) { + if (parent) { + enforceUserUnlocked(getProfileParentId(userId)); + } else { + enforceUserUnlocked(userId); + } + } + private void enforceManageUsers() { final int callingUid = mInjector.binderGetCallingUid(); if (!(isCallerWithSystemUid() || callingUid == Process.ROOT_UID)) {