Don't save password metrics to disk.
On FBE devices, don't save the metrics to disk but compute them when the
password is first entered and only store them in RAM.
Merged-in: 5daf273b7e3272269c53eda20ce494d0e7a365b5
Bug: 32793550
Change-Id: Icee7f615167761177b224b342970a36c7d90f6ba
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 5305473..78f0c92 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1981,7 +1981,8 @@
* 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. If the user has a password, it must have been entered in order to
+ * perform this check.
* <p>
* The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
@@ -1994,6 +1995,7 @@
* @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 IllegalStateException if the user has a password but has not entered it yet.
*/
public boolean isActivePasswordSufficient() {
if (mService != null) {
@@ -3428,6 +3430,19 @@
/**
* @hide
*/
+ public void reportPasswordChanged(int userId) {
+ if (mService != null) {
+ try {
+ mService.reportPasswordChanged(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
public void reportFailedPasswordAttempt(int userHandle) {
if (mService != null) {
try {
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index ddec412..2a679eb 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -116,6 +116,7 @@
void setActivePasswordState(int quality, int length, int letters, int uppercase, int lowercase,
int numbers, int symbols, int nonletter, 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 7f2f740..d79a5a4 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -565,9 +565,6 @@
setCredentialRequiredToDecrypt(false);
}
- getDevicePolicyManager().setActivePasswordState(
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0, userHandle);
-
onAfterChangingPassword(userHandle);
}
@@ -614,6 +611,7 @@
+ MIN_LOCK_PATTERN_SIZE + " dots long.");
}
+ setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId);
getLockSettings().setLockPattern(patternToString(pattern), savedPattern, userId);
DevicePolicyManager dpm = getDevicePolicyManager();
@@ -630,9 +628,6 @@
setBoolean(PATTERN_EVER_CHOSEN_KEY, true, userId);
- setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId);
- dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING,
- pattern.size(), 0, 0, 0, 0, 0, 0, userId);
onAfterChangingPassword(userId);
} catch (RemoteException re) {
Log.e(TAG, "Couldn't save lock pattern " + re);
@@ -835,9 +830,9 @@
+ "of length " + MIN_LOCK_PASSWORD_SIZE);
}
+ final int computedQuality = computePasswordQuality(password);
+ setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality), userHandle);
getLockSettings().setLockPassword(password, savedPassword, userHandle);
- getLockSettings().setSeparateProfileChallengeEnabled(userHandle, true, null);
- int computedQuality = computePasswordQuality(password);
// Update the device encryption password.
if (userHandle == UserHandle.USER_SYSTEM
@@ -855,40 +850,6 @@
}
}
- setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality), userHandle);
- if (computedQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
- int letters = 0;
- int uppercase = 0;
- int lowercase = 0;
- int numbers = 0;
- int symbols = 0;
- int nonletter = 0;
- for (int i = 0; i < password.length(); i++) {
- char c = password.charAt(i);
- if (c >= 'A' && c <= 'Z') {
- letters++;
- uppercase++;
- } else if (c >= 'a' && c <= 'z') {
- letters++;
- lowercase++;
- } else if (c >= '0' && c <= '9') {
- numbers++;
- nonletter++;
- } else {
- symbols++;
- nonletter++;
- }
- }
- dpm.setActivePasswordState(Math.max(quality, computedQuality),
- password.length(), letters, uppercase, lowercase,
- numbers, symbols, nonletter, userHandle);
- } else {
- // The password is not anything.
- dpm.setActivePasswordState(
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
- 0, 0, 0, 0, 0, 0, 0, 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 42e75c6..80088b7 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -899,6 +899,7 @@
synchronized (mSeparateChallengeLock) {
setLockPatternInternal(pattern, savedCredential, userId);
setSeparateProfileChallengeEnabled(userId, true, null);
+ notifyPasswordChanged(userId);
}
}
@@ -913,6 +914,7 @@
setKeystorePassword(null, userId);
fixateNewestUserKeyAuth(userId);
onUserLockChanged(userId);
+ notifyActivePasswordMetricsAvailable(null, userId);
return;
}
@@ -962,6 +964,7 @@
synchronized (mSeparateChallengeLock) {
setLockPasswordInternal(password, savedCredential, userId);
setSeparateProfileChallengeEnabled(userId, true, null);
+ notifyPasswordChanged(userId);
}
}
@@ -975,6 +978,7 @@
setKeystorePassword(null, userId);
fixateNewestUserKeyAuth(userId);
onUserLockChanged(userId);
+ notifyActivePasswordMetricsAvailable(null, userId);
return;
}
@@ -1365,6 +1369,7 @@
// 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,
@@ -1398,6 +1403,7 @@
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
// credential has matched
+ notifyActivePasswordMetricsAvailable(credential, userId);
unlockKeystore(credential, userId);
Slog.i(TAG, "Unlocking user " + userId +
@@ -1421,6 +1427,60 @@
return response;
}
+ private void notifyActivePasswordMetricsAvailable(final String password, int userId) {
+ final int quality = mLockPatternUtils.getKeyguardStoredPasswordQuality(userId);
+
+ // Asynchronous to avoid dead lock
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ int length = 0;
+ int letters = 0;
+ int uppercase = 0;
+ int lowercase = 0;
+ int numbers = 0;
+ int symbols = 0;
+ int nonletter = 0;
+ if (password != null) {
+ length = password.length();
+ for (int i = 0; i < length; i++) {
+ char c = password.charAt(i);
+ if (c >= 'A' && c <= 'Z') {
+ letters++;
+ uppercase++;
+ } else if (c >= 'a' && c <= 'z') {
+ letters++;
+ lowercase++;
+ } else if (c >= '0' && c <= '9') {
+ numbers++;
+ nonletter++;
+ } else {
+ symbols++;
+ nonletter++;
+ }
+ }
+ }
+ DevicePolicyManager dpm = (DevicePolicyManager)
+ mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ dpm.setActivePasswordState(quality, length, letters, uppercase, lowercase, numbers,
+ symbols, nonletter, userId);
+ }
+ });
+ }
+
+ /**
+ * Call after {@link #notifyActivePasswordMetricsAvailable} so metrics are updated before
+ * reporting the password changed.
+ */
+ private void notifyPasswordChanged(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 2aaf945..80dfe66 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2218,10 +2218,13 @@
out.endTag(null, "failed-password-attempts");
}
- if (policy.mActivePasswordQuality != 0 || policy.mActivePasswordLength != 0
+ // Don't save metrics for FBE devices
+ if (!mInjector.storageManagerIsFileBasedEncryptionEnabled()
+ && (policy.mActivePasswordQuality != 0 || policy.mActivePasswordLength != 0
|| policy.mActivePasswordUpperCase != 0 || policy.mActivePasswordLowerCase != 0
|| policy.mActivePasswordLetters != 0 || policy.mActivePasswordNumeric != 0
- || policy.mActivePasswordSymbols != 0 || policy.mActivePasswordNonLetter != 0) {
+ || policy.mActivePasswordSymbols != 0
+ || policy.mActivePasswordNonLetter != 0)) {
out.startTag(null, "active-password");
out.attribute(null, "quality", Integer.toString(policy.mActivePasswordQuality));
out.attribute(null, "length", Integer.toString(policy.mActivePasswordLength));
@@ -2314,6 +2317,7 @@
JournaledFile journal = makeJournaledFile(userHandle);
FileInputStream stream = null;
File file = journal.chooseForRead();
+ boolean needsRewrite = false;
try {
stream = new FileInputStream(file);
XmlPullParser parser = Xml.newPullParser();
@@ -2390,23 +2394,6 @@
} else if ("password-owner".equals(tag)) {
policy.mPasswordOwner = Integer.parseInt(
parser.getAttributeValue(null, "value"));
- } else if ("active-password".equals(tag)) {
- policy.mActivePasswordQuality = Integer.parseInt(
- parser.getAttributeValue(null, "quality"));
- policy.mActivePasswordLength = Integer.parseInt(
- parser.getAttributeValue(null, "length"));
- policy.mActivePasswordUpperCase = Integer.parseInt(
- parser.getAttributeValue(null, "uppercase"));
- policy.mActivePasswordLowerCase = Integer.parseInt(
- parser.getAttributeValue(null, "lowercase"));
- policy.mActivePasswordLetters = Integer.parseInt(
- parser.getAttributeValue(null, "letters"));
- policy.mActivePasswordNumeric = Integer.parseInt(
- parser.getAttributeValue(null, "numeric"));
- policy.mActivePasswordSymbols = Integer.parseInt(
- parser.getAttributeValue(null, "symbols"));
- policy.mActivePasswordNonLetter = 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)) {
@@ -2423,6 +2410,28 @@
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)) {
+ if (mInjector.storageManagerIsFileBasedEncryptionEnabled()) {
+ // Remove this from FBE devices
+ needsRewrite = true;
+ } else {
+ policy.mActivePasswordQuality = Integer.parseInt(
+ parser.getAttributeValue(null, "quality"));
+ policy.mActivePasswordLength = Integer.parseInt(
+ parser.getAttributeValue(null, "length"));
+ policy.mActivePasswordUpperCase = Integer.parseInt(
+ parser.getAttributeValue(null, "uppercase"));
+ policy.mActivePasswordLowerCase = Integer.parseInt(
+ parser.getAttributeValue(null, "lowercase"));
+ policy.mActivePasswordLetters = Integer.parseInt(
+ parser.getAttributeValue(null, "letters"));
+ policy.mActivePasswordNumeric = Integer.parseInt(
+ parser.getAttributeValue(null, "numeric"));
+ policy.mActivePasswordSymbols = Integer.parseInt(
+ parser.getAttributeValue(null, "symbols"));
+ policy.mActivePasswordNonLetter = Integer.parseInt(
+ parser.getAttributeValue(null, "nonletter"));
+ }
} else {
Slog.w(LOG_TAG, "Unknown tag: " + tag);
XmlUtils.skipCurrentTag(parser);
@@ -2442,34 +2451,14 @@
// 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.mActivePasswordQuality) {
- Slog.w(LOG_TAG, "Active password quality 0x"
- + Integer.toHexString(policy.mActivePasswordQuality)
- + " does not match actual quality 0x"
- + Integer.toHexString(actualPasswordQuality));
- policy.mActivePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
- policy.mActivePasswordLength = 0;
- policy.mActivePasswordUpperCase = 0;
- policy.mActivePasswordLowerCase = 0;
- policy.mActivePasswordLetters = 0;
- policy.mActivePasswordNumeric = 0;
- policy.mActivePasswordSymbols = 0;
- policy.mActivePasswordNonLetter = 0;
- }
- } finally {
- mInjector.binderRestoreCallingIdentity(identity);
- }
-
validatePasswordOwnerLocked(policy);
updateMaximumTimeToLockLocked(userHandle);
updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle);
@@ -3663,6 +3652,8 @@
private boolean isActivePasswordSufficientForUserLocked(
DevicePolicyData policy, int userHandle, boolean parent) {
+ enforceUserUnlocked(userHandle, parent);
+
if (policy.mActivePasswordQuality < getPasswordQuality(null, userHandle, parent)
|| policy.mActivePasswordLength < getPasswordMinimumLength(
null, userHandle, parent)) {
@@ -4673,40 +4664,66 @@
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)) {
+ quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+ length = 0;
+ letters = 0;
+ uppercase = 0;
+ lowercase = 0;
+ numbers = 0;
+ symbols = 0;
+ nonletter = 0;
+ }
+
+ validateQualityConstant(quality);
+ DevicePolicyData policy = getUserData(userHandle);
+ synchronized (this) {
+ policy.mActivePasswordQuality = quality;
+ policy.mActivePasswordLength = length;
+ policy.mActivePasswordLetters = letters;
+ policy.mActivePasswordLowerCase = lowercase;
+ policy.mActivePasswordUpperCase = uppercase;
+ policy.mActivePasswordNumeric = numbers;
+ policy.mActivePasswordSymbols = symbols;
+ policy.mActivePasswordNonLetter = nonletter;
+ }
+ }
+
+ @Override
+ public void reportPasswordChanged(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(quality);
- DevicePolicyData policy = getUserData(userHandle);
+ DevicePolicyData policy = getUserData(userId);
long ident = mInjector.binderClearCallingIdentity();
try {
synchronized (this) {
- policy.mActivePasswordQuality = quality;
- policy.mActivePasswordLength = length;
- policy.mActivePasswordLetters = letters;
- policy.mActivePasswordLowerCase = lowercase;
- policy.mActivePasswordUpperCase = uppercase;
- policy.mActivePasswordNumeric = numbers;
- policy.mActivePasswordSymbols = symbols;
- policy.mActivePasswordNonLetter = nonletter;
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);
}
@@ -6278,6 +6295,14 @@
"User must be running and unlocked");
}
+ private void enforceUserUnlocked(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)) {