Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f7ca81fb authored by Andrew Scull's avatar Andrew Scull Committed by Android (Google) Code Review
Browse files

Merge "DPMS: password blacklist"

parents e51ffaa6 7f31bb04
Loading
Loading
Loading
Loading
+112 −1
Original line number Diff line number Diff line
@@ -2634,11 +2634,122 @@ public class DevicePolicyManager {
        return 16;
    }

    /**
     * The maximum number of characters allowed in the password blacklist.
     */
    private static final int PASSWORD_BLACKLIST_CHARACTER_LIMIT = 128 * 1000;

    /**
     * Throws an exception if the password blacklist is too large.
     *
     * @hide
     */
    public static void enforcePasswordBlacklistSize(List<String> blacklist) {
        if (blacklist == null) {
            return;
        }
        long characterCount = 0;
        for (final String item : blacklist) {
            characterCount += item.length();
        }
        if (characterCount > PASSWORD_BLACKLIST_CHARACTER_LIMIT) {
            throw new IllegalArgumentException("128 thousand blacklist character limit exceeded by "
                      + (characterCount - PASSWORD_BLACKLIST_CHARACTER_LIMIT) + " characters");
        }
    }

    /**
     * Called by an application that is administering the device to blacklist passwords.
     * <p>
     * Any blacklisted password or PIN is prevented from being enrolled by the user or the admin.
     * Note that the match against the blacklist is case insensitive. The blacklist applies for all
     * password qualities requested by {@link #setPasswordQuality} however it is not taken into
     * consideration by {@link #isActivePasswordSufficient}.
     * <p>
     * The blacklist can be cleared by passing {@code null} or an empty list. The blacklist is
     * given a name that is used to track which blacklist is currently set by calling {@link
     * #getPasswordBlacklistName}. If the blacklist is being cleared, the name is ignored and {@link
     * #getPasswordBlacklistName} will return {@code null}. The name can only be {@code null} when
     * the blacklist is being cleared.
     * <p>
     * The blacklist is limited to a total of 128 thousand characters rather than limiting to a
     * number of entries.
     * <p>
     * This method can be called on the {@link DevicePolicyManager} instance returned by
     * {@link #getParentProfileInstance(ComponentName)} in order to set restrictions on the parent
     * profile.
     *
     * @param admin the {@link DeviceAdminReceiver} this request is associated with
     * @param name name to associate with the blacklist
     * @param blacklist list of passwords to blacklist or {@code null} to clear the blacklist
     * @return whether the new blacklist was successfully installed
     * @throws SecurityException if {@code admin} is not a device or profile owner
     * @throws IllegalArgumentException if the blacklist surpasses the character limit
     * @throws NullPointerException if {@code name} is {@code null} when setting a non-empty list
     *
     * @see #getPasswordBlacklistName
     * @see #isActivePasswordSufficient
     * @see #resetPasswordWithToken
     *
     * TODO(63578054): unhide for P
     * @hide
     */
    public boolean setPasswordBlacklist(@NonNull ComponentName admin, @Nullable String name,
            @Nullable List<String> blacklist) {
        enforcePasswordBlacklistSize(blacklist);

        try {
            return mService.setPasswordBlacklist(admin, name, blacklist, mParentInstance);
        } catch (RemoteException re) {
            throw re.rethrowFromSystemServer();
        }
    }

    /**
     * Get the name of the password blacklist set by the given admin.
     *
     * @param admin the {@link DeviceAdminReceiver} this request is associated with
     * @return the name of the blacklist or {@code null} if no blacklist is set
     *
     * @see #setPasswordBlacklist
     *
     * TODO(63578054): unhide for P
     * @hide
     */
    public @Nullable String getPasswordBlacklistName(@NonNull ComponentName admin) {
        try {
            return mService.getPasswordBlacklistName(admin, myUserId(), mParentInstance);
        } catch (RemoteException re) {
            throw re.rethrowFromSystemServer();
        }
    }

    /**
     * Test if a given password is blacklisted.
     *
     * @param userId the user to valiate for
     * @param password the password to check against the blacklist
     * @return whether the password is blacklisted
     *
     * @see #setPasswordBlacklist
     *
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.TEST_BLACKLISTED_PASSWORD)
    public boolean isPasswordBlacklisted(@UserIdInt int userId, @NonNull String password) {
        try {
            return mService.isPasswordBlacklisted(userId, password);
        } catch (RemoteException re) {
            throw re.rethrowFromSystemServer();
        }
    }

    /**
     * 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. The user must be unlocked in order to perform the check.
     * are not taken into account. The user must be unlocked in order to perform the check. The
     * password blacklist is not considered when checking sufficiency.
     * <p>
     * The calling device admin must have requested
     * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
+4 −0
Original line number Diff line number Diff line
@@ -78,6 +78,10 @@ interface IDevicePolicyManager {

    long getPasswordExpiration(in ComponentName who, int userHandle, boolean parent);

    boolean setPasswordBlacklist(in ComponentName who, String name, in List<String> blacklist, boolean parent);
    String getPasswordBlacklistName(in ComponentName who, int userId, boolean parent);
    boolean isPasswordBlacklisted(int userId, String password);

    boolean isActivePasswordSufficient(int userHandle, boolean parent);
    boolean isProfileActivePasswordSufficientForParent(int userHandle);
    boolean isUsingUnifiedPassword(in ComponentName admin);
+5 −0
Original line number Diff line number Diff line
@@ -1620,6 +1620,11 @@
    <permission android:name="android.permission.ACCESS_PDB_STATE"
        android:protectionLevel="signature" />

    <!-- Allows testing if a passwords is forbidden by the admins.
         @hide <p>Not for use by third-party applications. -->
    <permission android:name="android.permission.TEST_BLACKLISTED_PASSWORD"
        android:protectionLevel="signature" />

    <!-- @hide Allows system update service to notify device owner about pending updates.
   <p>Not for use by third-party applications. -->
    <permission android:name="android.permission.NOTIFY_PENDING_SYSTEM_UPDATE"
+20 −0
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
 */
package com.android.server.devicepolicy;

import android.annotation.UserIdInt;
import android.app.admin.IDevicePolicyManager;
import android.content.ComponentName;
import android.os.PersistableBundle;
@@ -24,6 +25,8 @@ import android.security.keystore.ParcelableKeyGenParameterSpec;
import com.android.internal.R;
import com.android.server.SystemService;

import java.util.List;

/**
 * Defines the required interface for IDevicePolicyManager implemenation.
 *
@@ -68,6 +71,23 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub {
        return false;
    }

    @Override
    public boolean setPasswordBlacklist(ComponentName who, String name, List<String> blacklist,
            boolean parent) {
        return false;
    }

    @Override
    public String getPasswordBlacklistName(ComponentName who, @UserIdInt int userId,
            boolean parent) {
        return null;
    }

    @Override
    public boolean isPasswordBlacklisted(@UserIdInt int userId, String password) {
        return false;
    }

    public boolean isUsingUnifiedPassword(ComponentName who) {
        return true;
    }
+162 −6
Original line number Diff line number Diff line
@@ -734,6 +734,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        private static final String TAG_PASSWORD_HISTORY_LENGTH = "password-history-length";
        private static final String TAG_MIN_PASSWORD_LENGTH = "min-password-length";
        private static final String ATTR_VALUE = "value";
        private static final String TAG_PASSWORD_BLACKLIST = "password-blacklist";
        private static final String TAG_PASSWORD_QUALITY = "password-quality";
        private static final String TAG_POLICIES = "policies";
        private static final String TAG_CROSS_PROFILE_WIDGET_PROVIDERS =
@@ -866,6 +867,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        // Default title of confirm credentials screen
        String organizationName = null;

        // The blacklist data is stored in a file whose name is stored in the XML
        String passwordBlacklistFile = null;

        ActiveAdmin(DeviceAdminInfo _info, boolean parent) {
            info = _info;
            isParent = parent;
@@ -947,6 +951,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
                    out.endTag(null, TAG_MIN_PASSWORD_NONLETTER);
                }
            }
            if (passwordBlacklistFile != null) {
                out.startTag(null, TAG_PASSWORD_BLACKLIST);
                out.attribute(null, ATTR_VALUE, passwordBlacklistFile);
                out.endTag(null, TAG_PASSWORD_BLACKLIST);
            }
            if (maximumTimeToUnlock != DEF_MAXIMUM_TIME_TO_UNLOCK) {
                out.startTag(null, TAG_MAX_TIME_TO_UNLOCK);
                out.attribute(null, ATTR_VALUE, Long.toString(maximumTimeToUnlock));
@@ -1186,6 +1195,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
                } else if (TAG_MIN_PASSWORD_NONLETTER.equals(tag)) {
                    minimumPasswordMetrics.nonLetter = Integer.parseInt(
                            parser.getAttributeValue(null, ATTR_VALUE));
                } else if (TAG_PASSWORD_BLACKLIST.equals(tag)) {
                    passwordBlacklistFile = parser.getAttributeValue(null, ATTR_VALUE);
                }else if (TAG_MAX_TIME_TO_UNLOCK.equals(tag)) {
                    maximumTimeToUnlock = Long.parseLong(
                            parser.getAttributeValue(null, ATTR_VALUE));
@@ -1441,6 +1452,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
                    pw.println(minimumPasswordMetrics.symbols);
            pw.print(prefix); pw.print("minimumPasswordNonLetter=");
                    pw.println(minimumPasswordMetrics.nonLetter);
            pw.print(prefix); pw.print("passwordBlacklist=");
                    pw.println(passwordBlacklistFile != null);
            pw.print(prefix); pw.print("maximumTimeToUnlock=");
                    pw.println(maximumTimeToUnlock);
            pw.print(prefix); pw.print("strongAuthUnlockTimeout=");
@@ -1693,6 +1706,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
            return new LockPatternUtils(mContext);
        }

        PasswordBlacklist newPasswordBlacklist(File file) {
            return new PasswordBlacklist(file);
        }

        boolean storageManagerIsFileBasedEncryptionEnabled() {
            return StorageManager.isFileEncryptedNativeOnly();
        }
@@ -2589,11 +2606,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        }
    }

    private JournaledFile makeJournaledFile(int userHandle) {
        final String base = userHandle == UserHandle.USER_SYSTEM
                ? mInjector.getDevicePolicyFilePathForSystemUser() + DEVICE_POLICIES_XML
                : new File(mInjector.environmentGetUserSystemDirectory(userHandle),
                        DEVICE_POLICIES_XML).getAbsolutePath();
    private File getPolicyFileDirectory(@UserIdInt int userId) {
        return userId == UserHandle.USER_SYSTEM
                ? new File(mInjector.getDevicePolicyFilePathForSystemUser())
                : mInjector.environmentGetUserSystemDirectory(userId);
    }

    private JournaledFile makeJournaledFile(@UserIdInt int userId) {
        final String base = new File(getPolicyFileDirectory(userId), DEVICE_POLICIES_XML)
                .getAbsolutePath();
        if (VERBOSE_LOG) {
            Log.v(LOG_TAG, "Opening " + base);
        }
@@ -4064,6 +4085,136 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        }
    }

    /* @return the password blacklist set by the admin or {@code null} if none. */
    PasswordBlacklist getAdminPasswordBlacklistLocked(@NonNull ActiveAdmin admin) {
        final int userId = UserHandle.getUserId(admin.getUid());
        return admin.passwordBlacklistFile == null ? null : new PasswordBlacklist(
                new File(getPolicyFileDirectory(userId), admin.passwordBlacklistFile));
    }

    private static final String PASSWORD_BLACKLIST_FILE_PREFIX = "password-blacklist-";
    private static final String PASSWORD_BLACKLIST_FILE_SUFFIX = "";

    @Override
    public boolean setPasswordBlacklist(ComponentName who, String name, List<String> blacklist,
            boolean parent) {
        if (!mHasFeature) {
            return false;
        }
        Preconditions.checkNotNull(who, "who is null");

        synchronized (this) {
            final ActiveAdmin admin = getActiveAdminForCallerLocked(
                    who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, parent);
            final int userId = mInjector.userHandleGetCallingUserId();
            PasswordBlacklist adminBlacklist = getAdminPasswordBlacklistLocked(admin);

            if (blacklist == null || blacklist.isEmpty()) {
                // Remove the adminBlacklist
                admin.passwordBlacklistFile = null;
                saveSettingsLocked(userId);
                if (adminBlacklist != null) {
                    adminBlacklist.delete();
                }
                return true;
            }

            // Validate server side
            Preconditions.checkNotNull(name, "name is null");
            DevicePolicyManager.enforcePasswordBlacklistSize(blacklist);

            // Blacklist is case insensitive so normalize to lower case
            final int blacklistSize = blacklist.size();
            for (int i = 0; i < blacklistSize; ++i) {
                blacklist.set(i, blacklist.get(i).toLowerCase());
            }

            final boolean isNewBlacklist = adminBlacklist == null;
            if (isNewBlacklist) {
                // Create a new file for the blacklist. There could be multiple admins, each setting
                // different blacklists, to restrict a user's credential, for example a managed
                // profile can impose restrictions on its parent while the parent is already
                // restricted by its own admin. A deterministic naming scheme would be fragile if
                // new types of admin are introduced so we generate and save the file name instead.
                // This isn't a temporary file but it reuses the name generation logic
                final File file;
                try {
                    file = File.createTempFile(PASSWORD_BLACKLIST_FILE_PREFIX,
                            PASSWORD_BLACKLIST_FILE_SUFFIX, getPolicyFileDirectory(userId));
                } catch (IOException e) {
                    Slog.e(LOG_TAG, "Failed to make a file for the blacklist", e);
                    return false;
                }
                adminBlacklist = mInjector.newPasswordBlacklist(file);
            }

            if (adminBlacklist.savePasswordBlacklist(name, blacklist)) {
                if (isNewBlacklist) {
                    // The blacklist was saved so point the admin to the file
                    admin.passwordBlacklistFile = adminBlacklist.getFile().getName();
                    saveSettingsLocked(userId);
                }
                return true;
            }
        }

        return false;
    }

    @Override
    public String getPasswordBlacklistName(ComponentName who, @UserIdInt int userId,
            boolean parent) {
        if (!mHasFeature) {
            return null;
        }
        Preconditions.checkNotNull(who, "who is null");
        enforceFullCrossUsersPermission(userId);
        synchronized (this) {
            final ActiveAdmin admin = getActiveAdminForCallerLocked(
                    who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, parent);
            final PasswordBlacklist blacklist = getAdminPasswordBlacklistLocked(admin);
            if (blacklist == null) {
                return null;
            }
            return blacklist.getName();
        }
    }

    @Override
    public boolean isPasswordBlacklisted(@UserIdInt int userId, String password) {
        if (!mHasFeature) {
            return false;
        }
        mContext.enforceCallingOrSelfPermission(
                android.Manifest.permission.TEST_BLACKLISTED_PASSWORD, null);
        return isPasswordBlacklistedInternal(userId, password);
    }

    private boolean isPasswordBlacklistedInternal(@UserIdInt int userId, String password) {
        Preconditions.checkNotNull(password, "Password is null");
        enforceFullCrossUsersPermission(userId);

        // Normalize to lower case for case insensitive blacklist match
        final String lowerCasePassword = password.toLowerCase();

        synchronized (this) {
            final List<ActiveAdmin> admins =
                    getActiveAdminsForLockscreenPoliciesLocked(userId, /* parent */ false);
            final int N = admins.size();
            for (int i = 0; i < N; i++) {
                final PasswordBlacklist blacklist
                        = getAdminPasswordBlacklistLocked(admins.get(i));
                if (blacklist != null) {
                    if (blacklist.isPasswordBlacklisted(lowerCasePassword)) {
                        return true;
                    }
                }
            }
        }

        return false;
    }

    @Override
    public boolean isActivePasswordSufficient(int userHandle, boolean parent) {
        if (!mHasFeature) {
@@ -4420,6 +4571,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
                    return false;
                }
            }

            if (isPasswordBlacklistedInternal(userHandle, password)) {
                Slog.w(LOG_TAG, "resetPassword: the password is blacklisted");
                return false;
            }
        }

        DevicePolicyData policy = getUserData(userHandle);
Loading