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

Commit 45dc22e1 authored by Kholoud Mohamed's avatar Kholoud Mohamed Committed by Android (Google) Code Review
Browse files

Merge "Add set/verify lock credentials testAPIs in KeygyardManager" into sc-dev

parents ec46b714 60742372
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -281,6 +281,11 @@ package android.app {
    method public abstract void onHomeVisibilityChanged(boolean);
  }

  public class KeyguardManager {
    method @RequiresPermission(anyOf={android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS, "android.permission.ACCESS_KEYGUARD_SECURE_STORAGE"}) public boolean checkLock(int, @Nullable byte[]);
    method @RequiresPermission(anyOf={android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS, "android.permission.ACCESS_KEYGUARD_SECURE_STORAGE"}) public boolean setLock(int, @Nullable byte[], int, @Nullable byte[]);
  }

  public class Notification implements android.os.Parcelable {
    method public boolean shouldShowForegroundImmediately();
  }
+93 −37
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManager.PasswordComplexity;
import android.app.admin.PasswordMetrics;
@@ -51,6 +52,7 @@ import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternView;
import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.VerifyCredentialResponse;

import java.nio.charset.Charset;
import java.util.Arrays;
@@ -696,14 +698,15 @@ public class KeyguardManager {
    }

    private boolean checkInitialLockMethodUsage() {
        if (mContext.checkCallingOrSelfPermission(Manifest.permission.SET_INITIAL_LOCK)
                != PackageManager.PERMISSION_GRANTED) {
        if (!hasPermission(Manifest.permission.SET_INITIAL_LOCK)) {
            throw new SecurityException("Requires SET_INITIAL_LOCK permission.");
        }
        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
            return false;
        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
    }
        return true;

    private boolean hasPermission(String permission) {
        return PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
                permission);
    }

    /**
@@ -792,44 +795,97 @@ public class KeyguardManager {
            Log.e(TAG, "Password is not valid, rejecting call to setLock");
            return false;
        }
        boolean success = false;
        boolean success;
        try {
            LockscreenCredential credential = createLockscreenCredential(
                    lockType, password);
            success = lockPatternUtils.setLockCredential(
                    credential,
                    /* savedPassword= */ LockscreenCredential.createNone(),
                    userId);
        } catch (Exception e) {
            Log.e(TAG, "Save lock exception", e);
            success = false;
        } finally {
            Arrays.fill(password, (byte) 0);
        }
        return success;
    }

    /**
     * Set the lockscreen password to {@code newPassword} after validating the current password
     * against {@code currentPassword}.
     * <p>If no password is currently set, {@code currentPassword} should be set to {@code null}.
     * <p>To clear the current password, {@code newPassword} should be set to {@code null}.
     *
     * @return {@code true} if password successfully set.
     *
     * @throws IllegalArgumentException if {@code newLockType} or {@code currentLockType}
     * is invalid.
     *
     * @hide
     */
    @TestApi
    @RequiresPermission(anyOf = {
            Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS,
            Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE
    })
    public boolean setLock(@LockTypes int newLockType, @Nullable byte[] newPassword,
            @LockTypes int currentLockType, @Nullable byte[] currentPassword) {
        final LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext);
        final int userId = mContext.getUserId();
        LockscreenCredential currentCredential = createLockscreenCredential(
                currentLockType, currentPassword);
        LockscreenCredential newCredential = createLockscreenCredential(
                newLockType, newPassword);
        return lockPatternUtils.setLockCredential(newCredential, currentCredential, userId);
    }

    /**
     * Verifies the current lock credentials against {@code password}.
     * <p>To check if no password is set, {@code password} should be set to {@code null}.
     *
     * @return {@code true} if credentials match
     *
     * @throws IllegalArgumentException if {@code lockType} is invalid.
     *
     * @hide
     */
    @TestApi
    @RequiresPermission(anyOf = {
            Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS,
            Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE
    })
    public boolean checkLock(@LockTypes int lockType, @Nullable byte[] password) {
        final LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext);
        final LockscreenCredential credential = createLockscreenCredential(
                lockType, password);
        final VerifyCredentialResponse response = lockPatternUtils.verifyCredential(
                credential, mContext.getUserId(), /* flags= */ 0);
        if (response == null) {
            return false;
        }
        return response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK;
    }

    private LockscreenCredential createLockscreenCredential(
            @LockTypes int lockType, @Nullable byte[] password) {
        if (password == null) {
            return LockscreenCredential.createNone();
        }
        switch (lockType) {
            case PASSWORD:
                CharSequence passwordStr = new String(password, Charset.forName("UTF-8"));
                    lockPatternUtils.setLockCredential(
                            LockscreenCredential.createPassword(passwordStr),
                            /* savedPassword= */ LockscreenCredential.createNone(),
                            userId);
                    success = true;
                    break;
                return LockscreenCredential.createPassword(passwordStr);
            case PIN:
                CharSequence pinStr = new String(password);
                    lockPatternUtils.setLockCredential(
                            LockscreenCredential.createPin(pinStr),
                            /* savedPassword= */ LockscreenCredential.createNone(),
                            userId);
                    success = true;
                    break;
                return LockscreenCredential.createPin(pinStr);
            case PATTERN:
                List<LockPatternView.Cell> pattern =
                        LockPatternUtils.byteArrayToPattern(password);
                    lockPatternUtils.setLockCredential(
                            LockscreenCredential.createPattern(pattern),
                            /* savedPassword= */ LockscreenCredential.createNone(),
                            userId);
                    pattern.clear();
                    success = true;
                    break;
                return LockscreenCredential.createPattern(pattern);
            default:
                    Log.e(TAG, "Unknown lock type, returning a failure");
                throw new IllegalArgumentException("Unknown lock type " + lockType);
        }
        } catch (Exception e) {
            Log.e(TAG, "Save lock exception", e);
            success = false;
        } finally {
            Arrays.fill(password, (byte) 0);
        }
        return success;
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -95,4 +95,5 @@ interface ILockSettings {
    boolean hasSecureLockScreen();
    boolean tryUnlockWithCachedUnifiedChallenge(int userId);
    void removeCachedUnifiedChallenge(int userId);
    void updateEncryptionPassword(int type, in byte[] password);
}
+13 −178
Original line number Diff line number Diff line
@@ -34,7 +34,6 @@ import android.content.ComponentName;
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;
@@ -59,18 +58,13 @@ import com.android.server.LocalServices;

import com.google.android.collect.Lists;

import libcore.util.HexEncoding;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.StringJoiner;

/**
 * Utilities for the lock pattern and its settings.
@@ -186,7 +180,7 @@ public class LockPatternUtils {
    public static final String SYNTHETIC_PASSWORD_HANDLE_KEY = "sp-handle";
    public static final String SYNTHETIC_PASSWORD_ENABLED_KEY = "enable-sp";
    public static final int SYNTHETIC_PASSWORD_ENABLED_BY_DEFAULT = 1;
    private static final String HISTORY_DELIMITER = ",";
    public static final String PASSWORD_HISTORY_DELIMITER = ",";

    @UnsupportedAppUsage
    private final Context mContext;
@@ -559,9 +553,11 @@ public class LockPatternUtils {
        if(passwordHistoryLength == 0) {
            return false;
        }
        String legacyHash = legacyPasswordToHash(passwordToCheck, userId);
        String passwordHash = passwordToHistoryHash(passwordToCheck, hashFactor, userId);
        String[] history = passwordHistory.split(HISTORY_DELIMITER);
        byte[] salt = getSalt(userId).getBytes();
        String legacyHash = LockscreenCredential.legacyPasswordToHash(passwordToCheck, salt);
        String passwordHash = LockscreenCredential.passwordToHistoryHash(
                passwordToCheck, salt, hashFactor);
        String[] history = passwordHistory.split(PASSWORD_HISTORY_DELIMITER);
        // Password History may be too long...
        for (int i = 0; i < Math.min(passwordHistoryLength, history.length); i++) {
            if (history[i].equals(legacyHash) || history[i].equals(passwordHash)) {
@@ -701,20 +697,9 @@ public class LockPatternUtils {
        } catch (RemoteException e) {
            throw new RuntimeException("Unable to save lock password", e);
        }

        onPostPasswordChanged(newCredential, userHandle);
        return true;
    }

    private void onPostPasswordChanged(LockscreenCredential newCredential, int userHandle) {
        updateEncryptionPasswordIfNeeded(newCredential, userHandle);
        if (newCredential.isPattern()) {
            reportPatternWasChosen(userHandle);
        }
        updatePasswordHistory(newCredential, userHandle);
        reportEnabledTrustAgentsChanged(userHandle);
    }

    private void updateCryptoUserInfo(int userId) {
        if (userId != UserHandle.USER_SYSTEM) {
            return;
@@ -781,100 +766,6 @@ public class LockPatternUtils {
        return getDeviceOwnerInfo() != null;
    }

    /** Update the encryption password if it is enabled **/
    private void updateEncryptionPassword(final int type, final byte[] password) {
        if (!hasSecureLockScreen() && password != null && password.length != 0) {
            throw new UnsupportedOperationException(
                    "This operation requires the lock screen feature.");
        }
        if (!isDeviceEncryptionEnabled()) {
            return;
        }
        final IBinder service = ServiceManager.getService("mount");
        if (service == null) {
            Log.e(TAG, "Could not find the mount service to update the encryption password");
            return;
        }

        // TODO(b/120484642): This is a location where we still use a String for vold
        String passwordString = password != null ? new String(password) : null;
        new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... dummy) {
                IStorageManager storageManager = IStorageManager.Stub.asInterface(service);
                try {
                    storageManager.changeEncryptionPassword(type, passwordString);
                } catch (RemoteException e) {
                    Log.e(TAG, "Error changing encryption password", e);
                }
                return null;
            }
        }.execute();
    }

    /**
     * Update device encryption password if calling user is USER_SYSTEM and device supports
     * encryption.
     */
    private void updateEncryptionPasswordIfNeeded(LockscreenCredential credential, int userHandle) {
        // Update the device encryption password.
        if (userHandle != UserHandle.USER_SYSTEM || !isDeviceEncryptionEnabled()) {
            return;
        }
        if (!shouldEncryptWithCredentials(true)) {
            updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, null);
            return;
        }
        if (credential.isNone()) {
            // Set the encryption password to default.
            setCredentialRequiredToDecrypt(false);
        }
        updateEncryptionPassword(credential.getStorageCryptType(), credential.getCredential());
    }

    /**
     * Store the hash of the *current* password in the password history list, if device policy
     * enforces password history requirement.
     */
    private void updatePasswordHistory(LockscreenCredential password, int userHandle) {
        if (password.isNone()) {
            return;
        }
        if (password.isPattern()) {
            // Do not keep track of historical patterns
            return;
        }
        // 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);
        if (passwordHistory == null) {
            passwordHistory = "";
        }
        int passwordHistoryLength = getRequestedPasswordHistoryLength(userHandle);
        if (passwordHistoryLength == 0) {
            passwordHistory = "";
        } else {
            final byte[] hashFactor = getPasswordHistoryHashFactor(password, userHandle);
            String hash = passwordToHistoryHash(password.getCredential(), hashFactor, userHandle);
            if (hash == null) {
                Log.e(TAG, "Compute new style password hash failed, fallback to legacy style");
                hash = legacyPasswordToHash(password.getCredential(), userHandle);
            }
            if (TextUtils.isEmpty(passwordHistory)) {
                passwordHistory = hash;
            } else {
                String[] history = passwordHistory.split(HISTORY_DELIMITER);
                StringJoiner joiner = new StringJoiner(HISTORY_DELIMITER);
                joiner.add(hash);
                for (int i = 0; i < passwordHistoryLength - 1 && i < history.length; i++) {
                    joiner.add(history[i]);
                }
                passwordHistory = joiner.toString();
            }
        }
        setString(PASSWORD_HISTORY_KEY, passwordHistory, userHandle);
    }

    /**
     * Determine if the device supports encryption, even if it's set to default. This
     * differs from isDeviceEncrypted() in that it returns true even if the device is
@@ -898,7 +789,11 @@ public class LockPatternUtils {
     * Clears the encryption password.
     */
    public void clearEncryptionPassword() {
        updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, null);
        try {
            getLockSettings().updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, null);
        } catch (RemoteException e) {
            Log.e(TAG, "Couldn't clear encryption password");
        }
    }

    /**
@@ -1045,56 +940,9 @@ public class LockPatternUtils {
     * @param password the gesture pattern.
     *
     * @return the hash of the pattern in a byte array.
     * TODO: move to LockscreenCredential class
     */
    public String legacyPasswordToHash(byte[] password, int userId) {
        if (password == null || password.length == 0) {
            return null;
        }

        try {
            // Previously the password was passed as a String with the following code:
            // byte[] saltedPassword = (password + getSalt(userId)).getBytes();
            // The code below creates the identical digest preimage using byte arrays:
            byte[] salt = getSalt(userId).getBytes();
            byte[] saltedPassword = Arrays.copyOf(password, password.length + salt.length);
            System.arraycopy(salt, 0, saltedPassword, password.length, salt.length);
            byte[] sha1 = MessageDigest.getInstance("SHA-1").digest(saltedPassword);
            byte[] md5 = MessageDigest.getInstance("MD5").digest(saltedPassword);

            byte[] combined = new byte[sha1.length + md5.length];
            System.arraycopy(sha1, 0, combined, 0, sha1.length);
            System.arraycopy(md5, 0, combined, sha1.length, md5.length);

            final char[] hexEncoded = HexEncoding.encode(combined);
            Arrays.fill(saltedPassword, (byte) 0);
            return new String(hexEncoded);
        } catch (NoSuchAlgorithmException e) {
            throw new AssertionError("Missing digest algorithm: ", e);
        }
    }

    /**
     * Hash the password for password history check purpose.
     * TODO: move to LockscreenCredential class
     */
    private String passwordToHistoryHash(byte[] passwordToHash, byte[] hashFactor, int userId) {
        if (passwordToHash == null || passwordToHash.length == 0 || hashFactor == null) {
            return null;
        }
        try {
            MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
            sha256.update(hashFactor);
            byte[] salt = getSalt(userId).getBytes();
            byte[] saltedPassword = Arrays.copyOf(passwordToHash, passwordToHash.length
                    + salt.length);
            System.arraycopy(salt, 0, saltedPassword, passwordToHash.length, salt.length);
            sha256.update(saltedPassword);
            Arrays.fill(saltedPassword, (byte) 0);
            return new String(HexEncoding.encode(sha256.digest()));
        } catch (NoSuchAlgorithmException e) {
            throw new AssertionError("Missing digest algorithm: ", e);
        }
        return LockscreenCredential.legacyPasswordToHash(password, getSalt(userId).getBytes());
    }

    /**
@@ -1396,14 +1244,6 @@ public class LockPatternUtils {
        }
    }

    private boolean isDoNotAskCredentialsOnBootSet() {
        return getDevicePolicyManager().getDoNotAskCredentialsOnBoot();
    }

    private boolean shouldEncryptWithCredentials(boolean defaultValue) {
        return isCredentialRequiredToDecrypt(defaultValue) && !isDoNotAskCredentialsOnBootSet();
    }

    private void throwIfCalledOnMainThread() {
        if (Looper.getMainLooper().isCurrentThread()) {
            throw new IllegalStateException("should not be called from the main thread.");
@@ -1590,12 +1430,7 @@ public class LockPatternUtils {
        credential.checkLength();
        LockSettingsInternal localService = getLockSettingsInternal();

        if (!localService.setLockCredentialWithToken(credential, tokenHandle, token, userHandle)) {
            return false;
        }

        onPostPasswordChanged(credential, userHandle);
        return true;
        return localService.setLockCredentialWithToken(credential, tokenHandle, token, userHandle);
    }

    /**
+80 −0
Original line number Diff line number Diff line
@@ -31,6 +31,10 @@ import android.text.TextUtils;

import com.android.internal.util.Preconditions;

import libcore.util.HexEncoding;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@@ -276,6 +280,82 @@ public class LockscreenCredential implements Parcelable, AutoCloseable {
        return getType() == storedCredentialType;
    }

    /**
     * Hash the password for password history check purpose.
     */
    public String passwordToHistoryHash(byte[] salt, byte[] hashFactor) {
        return passwordToHistoryHash(mCredential, salt, hashFactor);
    }

    /**
     * Hash the password for password history check purpose.
     */
    public static String passwordToHistoryHash(
            byte[] passwordToHash, byte[] salt, byte[] hashFactor) {
        if (passwordToHash == null || passwordToHash.length == 0
                || hashFactor == null || salt == null) {
            return null;
        }
        try {
            MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
            sha256.update(hashFactor);
            byte[] saltedPassword = Arrays.copyOf(passwordToHash, passwordToHash.length
                    + salt.length);
            System.arraycopy(salt, 0, saltedPassword, passwordToHash.length, salt.length);
            sha256.update(saltedPassword);
            Arrays.fill(saltedPassword, (byte) 0);
            return new String(HexEncoding.encode(sha256.digest()));
        } catch (NoSuchAlgorithmException e) {
            throw new AssertionError("Missing digest algorithm: ", e);
        }
    }

    /**
     * Generate a hash for the given password. To avoid brute force attacks, we use a salted hash.
     * Not the most secure, but it is at least a second level of protection. First level is that
     * the file is in a location only readable by the system process.
     *
     * @return the hash of the pattern in a byte array.
     */
    public String legacyPasswordToHash(byte[] salt) {
        return legacyPasswordToHash(mCredential, salt);
    }

    /**
     * Generate a hash for the given password. To avoid brute force attacks, we use a salted hash.
     * Not the most secure, but it is at least a second level of protection. First level is that
     * the file is in a location only readable by the system process.
     *
     * @param password the gesture pattern.
     *
     * @return the hash of the pattern in a byte array.
     */
    public static String legacyPasswordToHash(byte[] password, byte[] salt) {
        if (password == null || password.length == 0 || salt == null) {
            return null;
        }

        try {
            // Previously the password was passed as a String with the following code:
            // byte[] saltedPassword = (password + salt).getBytes();
            // The code below creates the identical digest preimage using byte arrays:
            byte[] saltedPassword = Arrays.copyOf(password, password.length + salt.length);
            System.arraycopy(salt, 0, saltedPassword, password.length, salt.length);
            byte[] sha1 = MessageDigest.getInstance("SHA-1").digest(saltedPassword);
            byte[] md5 = MessageDigest.getInstance("MD5").digest(saltedPassword);

            byte[] combined = new byte[sha1.length + md5.length];
            System.arraycopy(sha1, 0, combined, 0, sha1.length);
            System.arraycopy(md5, 0, combined, sha1.length, md5.length);

            final char[] hexEncoded = HexEncoding.encode(combined);
            Arrays.fill(saltedPassword, (byte) 0);
            return new String(hexEncoded);
        } catch (NoSuchAlgorithmException e) {
            throw new AssertionError("Missing digest algorithm: ", e);
        }
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(mType);
Loading