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

Commit 9af061c6 authored by Rubin Xu's avatar Rubin Xu Committed by Android (Google) Code Review
Browse files

Merge "Introduce LockscreenCredential"

parents 176b8d09 a58125d2
Loading
Loading
Loading
Loading
+9 −13
Original line number Diff line number Diff line
@@ -27,9 +27,6 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
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.CREDENTIAL_TYPE_PATTERN;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.admin.DevicePolicyManager.PasswordComplexity;
@@ -37,8 +34,7 @@ import android.os.Parcel;
import android.os.Parcelable;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils.CredentialType;
import com.android.internal.widget.LockscreenCredential;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -232,15 +228,15 @@ public class PasswordMetrics implements Parcelable {
     * {@code credential} cannot be null when {@code type} is
     * {@link com.android.internal.widget.LockPatternUtils#CREDENTIAL_TYPE_PASSWORD}.
     */
    public static PasswordMetrics computeForCredential(
            @CredentialType int type, byte[] credential) {
        if (type == CREDENTIAL_TYPE_PASSWORD) {
            Preconditions.checkNotNull(credential, "credential cannot be null");
            return PasswordMetrics.computeForPassword(credential);
        } else if (type == CREDENTIAL_TYPE_PATTERN)  {
    public static PasswordMetrics computeForCredential(LockscreenCredential credential) {
        if (credential.isPassword()) {
            return PasswordMetrics.computeForPassword(credential.getCredential());
        } else if (credential.isPattern())  {
            return new PasswordMetrics(PASSWORD_QUALITY_SOMETHING);
        } else /* if (type == CREDENTIAL_TYPE_NONE) */ {
        } else if (credential.isNone()) {
            return new PasswordMetrics(PASSWORD_QUALITY_UNSPECIFIED);
        } else {
            throw new IllegalArgumentException("Unknown credential type " + credential.getType());
        }
    }

+36 −151
Original line number Diff line number Diff line
package com.android.internal.widget;

import android.annotation.UnsupportedAppUsage;
import android.os.AsyncTask;

import com.android.internal.widget.LockPatternUtils.RequestThrottledException;

import java.util.ArrayList;
import java.util.List;

/**
 * Helper class to check/verify PIN/Password/Pattern asynchronously.
 */
@@ -53,34 +49,28 @@ public final class LockPatternChecker {
    }

    /**
     * Verify a pattern asynchronously.
     * Verify a lockscreen credential asynchronously.
     *
     * @param utils The LockPatternUtils instance to use.
     * @param pattern The pattern to check.
     * @param challenge The challenge to verify against the pattern.
     * @param userId The user to check against the pattern.
     * @param credential The credential to check.
     * @param challenge The challenge to verify against the credential.
     * @param userId The user to check against the credential.
     * @param callback The callback to be invoked with the verification result.
     */
    public static AsyncTask<?, ?, ?> verifyPattern(final LockPatternUtils utils,
            final List<LockPatternView.Cell> pattern,
    public static AsyncTask<?, ?, ?> verifyCredential(final LockPatternUtils utils,
            final LockscreenCredential credential,
            final long challenge,
            final int userId,
            final OnVerifyCallback callback) {
        // Create a copy of the credential since checking credential is asynchrounous.
        final LockscreenCredential credentialCopy = credential.duplicate();
        AsyncTask<Void, Void, byte[]> task = new AsyncTask<Void, Void, byte[]>() {
            private int mThrottleTimeout;
            private List<LockPatternView.Cell> patternCopy;

            @Override
            protected void onPreExecute() {
                // Make a copy of the pattern to prevent race conditions.
                // No need to clone the individual cells because they are immutable.
                patternCopy = new ArrayList(pattern);
            }

            @Override
            protected byte[] doInBackground(Void... args) {
                try {
                    return utils.verifyPattern(patternCopy, challenge, userId);
                    return utils.verifyCredential(credentialCopy, challenge, userId);
                } catch (RequestThrottledException ex) {
                    mThrottleTimeout = ex.getTimeoutMs();
                    return null;
@@ -90,6 +80,12 @@ public final class LockPatternChecker {
            @Override
            protected void onPostExecute(byte[] result) {
                callback.onVerified(result, mThrottleTimeout);
                credentialCopy.zeroize();
            }

            @Override
            protected void onCancelled() {
                credentialCopy.zeroize();
            }
        };
        task.execute();
@@ -97,32 +93,26 @@ public final class LockPatternChecker {
    }

    /**
     * Checks a pattern asynchronously.
     * Checks a lockscreen credential asynchronously.
     *
     * @param utils The LockPatternUtils instance to use.
     * @param pattern The pattern to check.
     * @param userId The user to check against the pattern.
     * @param credential The credential to check.
     * @param userId The user to check against the credential.
     * @param callback The callback to be invoked with the check result.
     */
    public static AsyncTask<?, ?, ?> checkPattern(final LockPatternUtils utils,
            final List<LockPatternView.Cell> pattern,
    public static AsyncTask<?, ?, ?> checkCredential(final LockPatternUtils utils,
            final LockscreenCredential credential,
            final int userId,
            final OnCheckCallback callback) {
        // Create a copy of the credential since checking credential is asynchrounous.
        final LockscreenCredential credentialCopy = credential.duplicate();
        AsyncTask<Void, Void, Boolean> task = new AsyncTask<Void, Void, Boolean>() {
            private int mThrottleTimeout;
            private List<LockPatternView.Cell> patternCopy;

            @Override
            protected void onPreExecute() {
                // Make a copy of the pattern to prevent race conditions.
                // No need to clone the individual cells because they are immutable.
                patternCopy = new ArrayList(pattern);
            }

            @Override
            protected Boolean doInBackground(Void... args) {
                try {
                    return utils.checkPattern(patternCopy, userId, callback::onEarlyMatched);
                    return utils.checkCredential(credentialCopy, userId, callback::onEarlyMatched);
                } catch (RequestThrottledException ex) {
                    mThrottleTimeout = ex.getTimeoutMs();
                    return false;
@@ -132,11 +122,13 @@ public final class LockPatternChecker {
            @Override
            protected void onPostExecute(Boolean result) {
                callback.onChecked(result, mThrottleTimeout);
                credentialCopy.zeroize();
            }

            @Override
            protected void onCancelled() {
                callback.onCancelled();
                credentialCopy.zeroize();
            }
        };
        task.execute();
@@ -144,84 +136,29 @@ public final class LockPatternChecker {
    }

    /**
     * Verify a password asynchronously.
     * Perform a lockscreen credential verification explicitly on a managed profile with unified
     * challenge, using the parent user's credential.
     *
     * @param utils The LockPatternUtils instance to use.
     * @param password The password to check.
     * @param challenge The challenge to verify against the pattern.
     * @param userId The user to check against the pattern.
     * @param callback The callback to be invoked with the verification result.
     *
     * @deprecated Pass the password as a byte array.
     */
    @Deprecated
    public static AsyncTask<?, ?, ?> verifyPassword(final LockPatternUtils utils,
            final String password,
            final long challenge,
            final int userId,
            final OnVerifyCallback callback) {
        byte[] passwordBytes = password != null ? password.getBytes() : null;
        return verifyPassword(utils, passwordBytes, challenge, userId, callback);
    }

    /**
     * Verify a password asynchronously.
     *
     * @param utils The LockPatternUtils instance to use.
     * @param password The password to check.
     * @param challenge The challenge to verify against the pattern.
     * @param userId The user to check against the pattern.
     * @param callback The callback to be invoked with the verification result.
     */
    public static AsyncTask<?, ?, ?> verifyPassword(final LockPatternUtils utils,
            final byte[] password,
            final long challenge,
            final int userId,
            final OnVerifyCallback callback) {
        AsyncTask<Void, Void, byte[]> task = new AsyncTask<Void, Void, byte[]>() {
            private int mThrottleTimeout;

            @Override
            protected byte[] doInBackground(Void... args) {
                try {
                    return utils.verifyPassword(password, challenge, userId);
                } catch (RequestThrottledException ex) {
                    mThrottleTimeout = ex.getTimeoutMs();
                    return null;
                }
            }

            @Override
            protected void onPostExecute(byte[] result) {
                callback.onVerified(result, mThrottleTimeout);
            }
        };
        task.execute();
        return task;
    }

    /**
     * Verify a password asynchronously.
     *
     * @param utils The LockPatternUtils instance to use.
     * @param password The password to check.
     * @param challenge The challenge to verify against the pattern.
     * @param userId The user to check against the pattern.
     * @param credential The credential to check.
     * @param challenge The challenge to verify against the credential.
     * @param userId The user to check against the credential.
     * @param callback The callback to be invoked with the verification result.
     */
    public static AsyncTask<?, ?, ?> verifyTiedProfileChallenge(final LockPatternUtils utils,
            final byte[] password,
            final boolean isPattern,
            final LockscreenCredential credential,
            final long challenge,
            final int userId,
            final OnVerifyCallback callback) {
        // Create a copy of the credential since checking credential is asynchrounous.
        final LockscreenCredential credentialCopy = credential.duplicate();
        AsyncTask<Void, Void, byte[]> task = new AsyncTask<Void, Void, byte[]>() {
            private int mThrottleTimeout;

            @Override
            protected byte[] doInBackground(Void... args) {
                try {
                    return utils.verifyTiedProfileChallenge(password, isPattern, challenge, userId);
                    return utils.verifyTiedProfileChallenge(credentialCopy, challenge, userId);
                } catch (RequestThrottledException ex) {
                    mThrottleTimeout = ex.getTimeoutMs();
                    return null;
@@ -231,64 +168,12 @@ public final class LockPatternChecker {
            @Override
            protected void onPostExecute(byte[] result) {
                callback.onVerified(result, mThrottleTimeout);
            }
        };
        task.execute();
        return task;
    }

    /**
     * Checks a password asynchronously.
     *
     * @param utils The LockPatternUtils instance to use.
     * @param password The password to check.
     * @param userId The user to check against the pattern.
     * @param callback The callback to be invoked with the check result.
     * @deprecated Pass passwords as byte[]
     */
    @UnsupportedAppUsage
    @Deprecated
    public static AsyncTask<?, ?, ?> checkPassword(final LockPatternUtils utils,
            final String password,
            final int userId,
            final OnCheckCallback callback) {
        byte[] passwordBytes = password != null ? password.getBytes() : null;
        return checkPassword(utils, passwordBytes, userId, callback);
    }

    /**
     * Checks a password asynchronously.
     *
     * @param utils The LockPatternUtils instance to use.
     * @param passwordBytes The password to check.
     * @param userId The user to check against the pattern.
     * @param callback The callback to be invoked with the check result.
     */
    public static AsyncTask<?, ?, ?> checkPassword(final LockPatternUtils utils,
            final byte[] passwordBytes,
            final int userId,
            final OnCheckCallback callback) {
        AsyncTask<Void, Void, Boolean> task = new AsyncTask<Void, Void, Boolean>() {
            private int mThrottleTimeout;

            @Override
            protected Boolean doInBackground(Void... args) {
                try {
                    return utils.checkPassword(passwordBytes, userId, callback::onEarlyMatched);
                } catch (RequestThrottledException ex) {
                    mThrottleTimeout = ex.getTimeoutMs();
                    return false;
                }
            }

            @Override
            protected void onPostExecute(Boolean result) {
                callback.onChecked(result, mThrottleTimeout);
                credentialCopy.zeroize();
            }

            @Override
            protected void onCancelled() {
                callback.onCancelled();
                credentialCopy.zeroize();
            }
        };
        task.execute();
+163 −336

File changed.

Preview size limit exceeded, changes collapsed.

+1 −1
Original line number Diff line number Diff line
@@ -1318,7 +1318,7 @@ public class LockPatternView extends View {
        super.onRestoreInstanceState(ss.getSuperState());
        setPattern(
                DisplayMode.Correct,
                LockPatternUtils.stringToPattern(ss.getSerializedPattern()));
                LockPatternUtils.byteArrayToPattern(ss.getSerializedPattern().getBytes()));
        mPatternDisplayMode = DisplayMode.values()[ss.getDisplayMode()];
        mInputEnabled = ss.isInputEnabled();
        mInStealthMode = ss.isInStealthMode();
+294 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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 android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
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;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;

import com.android.internal.util.Preconditions;

import java.util.Arrays;
import java.util.List;

/**
 * A class representing a lockscreen credential. It can be either an empty password, a pattern
 * or a password (or PIN).
 *
 * <p> As required by some security certification, the framework tries its best to
 * remove copies of the lockscreen credential bytes from memory. In this regard, this class
 * abuses the {@link AutoCloseable} interface for sanitizing memory. This
 * presents a nice syntax to auto-zeroize memory with the try-with-resource statement:
 * <pre>
 * try {LockscreenCredential credential = LockscreenCredential.createPassword(...) {
 *     // Process the credential in some way
 * }
 * </pre>
 * With this construct, we can garantee that there will be no copies of the password left in
 * memory when the credential goes out of scope. This should help mitigate certain class of
 * attacks where the attcker gains read-only access to full device memory (cold boot attack,
 * unsecured software/hardware memory dumping interfaces such as JTAG).
 */
public class LockscreenCredential implements Parcelable, AutoCloseable {

    private final int mType;
    // Stores raw credential bytes, or null if credential has been zeroized. An empty password
    // is represented as a byte array of length 0.
    private byte[] mCredential;
    // Store the quality of the password, this is used to distinguish between pin
    // (PASSWORD_QUALITY_NUMERIC) and password (PASSWORD_QUALITY_ALPHABETIC).
    private final int mQuality;

    /**
     * Private constructor, use static builder methods instead.
     *
     * <p> Builder methods should create a private copy of the credential bytes and pass in here.
     * LockscreenCredential will only store the reference internally without copying. This is to
     * minimize the number of extra copies introduced.
     */
    private LockscreenCredential(int type, int quality, byte[] credential) {
        Preconditions.checkNotNull(credential);
        if (type == CREDENTIAL_TYPE_NONE) {
            Preconditions.checkArgument(credential.length == 0);
        } else {
            Preconditions.checkArgument(credential.length > 0);
        }
        mType = type;
        mQuality = quality;
        mCredential = credential;
    }

    /**
     * Creates a LockscreenCredential object representing empty password.
     */
    public static LockscreenCredential createNone() {
        return new LockscreenCredential(CREDENTIAL_TYPE_NONE, PASSWORD_QUALITY_UNSPECIFIED,
                new byte[0]);
    }

    /**
     * Creates a LockscreenCredential object representing the given pattern.
     */
    public static LockscreenCredential createPattern(@NonNull List<LockPatternView.Cell> pattern) {
        return new LockscreenCredential(CREDENTIAL_TYPE_PATTERN,
                PASSWORD_QUALITY_SOMETHING,
                LockPatternUtils.patternToByteArray(pattern));
    }

    /**
     * Creates a LockscreenCredential object representing the given alphabetic password.
     */
    public static LockscreenCredential createPassword(@NonNull CharSequence password) {
        return new LockscreenCredential(CREDENTIAL_TYPE_PASSWORD,
                PASSWORD_QUALITY_ALPHABETIC,
                charSequenceToByteArray(password));
    }

    /**
     * Creates a LockscreenCredential object representing the given numeric PIN.
     */
    public static LockscreenCredential createPin(@NonNull CharSequence pin) {
        return new LockscreenCredential(CREDENTIAL_TYPE_PASSWORD,
                PASSWORD_QUALITY_NUMERIC,
                charSequenceToByteArray(pin));
    }

    /**
     * Creates a LockscreenCredential object representing the given alphabetic password.
     * If the supplied password is empty, create an empty credential object.
     */
    public static LockscreenCredential createPasswordOrNone(@Nullable CharSequence password) {
        if (TextUtils.isEmpty(password)) {
            return createNone();
        } else {
            return createPassword(password);
        }
    }

    /**
     * Creates a LockscreenCredential object representing the given numeric PIN.
     * If the supplied password is empty, create an empty credential object.
     */
    public static LockscreenCredential createPinOrNone(@Nullable CharSequence pin) {
        if (TextUtils.isEmpty(pin)) {
            return createNone();
        } else {
            return createPin(pin);
        }
    }

    /**
     * Create a LockscreenCredential object based on raw credential and type
     * TODO: Remove once LSS.setUserPasswordMetrics accepts a LockscreenCredential
     */
    public static LockscreenCredential createRaw(int type, byte[] credential) {
        if (type == CREDENTIAL_TYPE_NONE) {
            return createNone();
        } else {
            return new LockscreenCredential(type, PASSWORD_QUALITY_UNSPECIFIED, credential);
        }
    }

    private void ensureNotZeroized() {
        Preconditions.checkState(mCredential != null, "Credential is already zeroized");
    }
    /**
     * Returns the type of this credential. Can be one of {@link #CREDENTIAL_TYPE_NONE},
     * {@link #CREDENTIAL_TYPE_PATTERN} or {@link #CREDENTIAL_TYPE_PASSWORD}.
     *
     * TODO: Remove once credential type is internal. Callers should use {@link #isNone},
     * {@link #isPattern} and {@link #isPassword} instead.
     */
    public int getType() {
        ensureNotZeroized();
        return mType;
    }

    /**
     * Returns the quality type of the credential
     */
    public int getQuality() {
        ensureNotZeroized();
        return mQuality;
    }

    /**
     * Returns the credential bytes. This is a direct reference of the internal field so
     * callers should not modify it.
     *
     */
    public byte[] getCredential() {
        ensureNotZeroized();
        return mCredential;
    }

    /** Returns whether this is an empty credential */
    public boolean isNone() {
        ensureNotZeroized();
        return mType == CREDENTIAL_TYPE_NONE;
    }

    /** Returns whether this is a pattern credential */
    public boolean isPattern() {
        ensureNotZeroized();
        return mType == CREDENTIAL_TYPE_PATTERN;
    }

    /** Returns whether this is a password credential */
    public boolean isPassword() {
        ensureNotZeroized();
        return mType == CREDENTIAL_TYPE_PASSWORD;
    }

    /** Returns the length of the credential */
    public int size() {
        ensureNotZeroized();
        return mCredential.length;
    }

    /** Create a copy of the credential */
    public LockscreenCredential duplicate() {
        return new LockscreenCredential(mType, mQuality,
                mCredential != null ? Arrays.copyOf(mCredential, mCredential.length) : null);
    }

    /**
     * Zeroize the credential bytes.
     */
    public void zeroize() {
        if (mCredential != null) {
            Arrays.fill(mCredential, (byte) 0);
            mCredential = null;
        }
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(mType);
        dest.writeInt(mQuality);
        dest.writeByteArray(mCredential);
    }

    public static final Parcelable.Creator<LockscreenCredential> CREATOR =
            new Parcelable.Creator<LockscreenCredential>() {

        @Override
        public LockscreenCredential createFromParcel(Parcel source) {
            return new LockscreenCredential(source.readInt(), source.readInt(),
                    source.createByteArray());
        }

        @Override
        public LockscreenCredential[] newArray(int size) {
            return new LockscreenCredential[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void close() {
        zeroize();
    }

    @Override
    public int hashCode() {
        // Effective Java — Item 9
        return ((17 + mType) * 31 + mQuality) * 31 + mCredential.hashCode();
    }

    @Override
    public boolean equals(Object o) {
        if (o == this) return true;
        if (!(o instanceof LockscreenCredential)) return false;
        final LockscreenCredential other = (LockscreenCredential) o;
        return mType == other.mType && mQuality == other.mQuality
                && Arrays.equals(mCredential, other.mCredential);
    }

    /**
     * Converts a CharSequence to a byte array without requiring a toString(), which creates an
     * additional copy.
     *
     * @param chars The CharSequence to convert
     * @return A byte array representing the input
     */
    private static byte[] charSequenceToByteArray(CharSequence chars) {
        if (chars == null) {
            return new byte[0];
        }
        byte[] bytes = new byte[chars.length()];
        for (int i = 0; i < chars.length(); i++) {
            bytes[i] = (byte) chars.charAt(i);
        }
        return bytes;
    }
}
Loading