blob: 0a6e07524071c19a2fc01004573b7bacb7da5a64 [file] [log] [blame]
/*
* Copyright (C) 2018 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.car.settings.security;
import android.app.admin.DevicePolicyManager;
import android.app.admin.PasswordMetrics;
import android.content.Context;
import com.android.car.settings.R;
import com.android.car.settings.common.Logger;
import com.android.car.setupwizardlib.InitialLockSetupConstants.ValidateLockFlags;
import com.android.internal.widget.LockscreenCredential;
import java.util.LinkedList;
import java.util.List;
/**
* Helper used by ChooseLockPinPasswordFragment
*/
public class PasswordHelper {
public static final String EXTRA_CURRENT_SCREEN_LOCK = "extra_current_screen_lock";
public static final String EXTRA_CURRENT_PASSWORD_QUALITY = "extra_current_password_quality";
/**
* Required minimum length of PIN or password.
*/
public static final int MIN_LENGTH = 4;
// Error code returned from validate(String).
static final int NO_ERROR = 0;
static final int CONTAINS_INVALID_CHARACTERS = 1;
static final int TOO_SHORT = 1 << 1;
static final int CONTAINS_NON_DIGITS = 1 << 2;
static final int CONTAINS_SEQUENTIAL_DIGITS = 1 << 3;
private static final Logger LOG = new Logger(PasswordHelper.class);
private final boolean mIsPin;
public PasswordHelper(boolean isPin) {
mIsPin = isPin;
}
/**
* Returns one of the password quality values defined in {@link DevicePolicyManager}, such
* as NUMERIC, ALPHANUMERIC etc.
*/
public int getPasswordQuality() {
return mIsPin ? DevicePolicyManager.PASSWORD_QUALITY_NUMERIC :
DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
}
/**
* Validates PIN/Password and returns the validation result.
*
* @param password the raw bytes for the password the user typed in
* @return the error code which should be non-zero where there is error. Otherwise
* {@link #NO_ERROR} should be returned.
*/
public int validate(LockscreenCredential password) {
return mIsPin ? validatePin(password.getCredential())
: validatePassword(password.getCredential());
}
/**
* Validates PIN/Password using the setup wizard {@link ValidateLockFlags}.
*
* @param password The password to validate.
* @return The error code where 0 is no error.
*/
public int validateSetupWizard(byte[] password) {
return mIsPin ? translateSettingsToSuwError(validatePin(password))
: translateSettingsToSuwError(validatePassword(password));
}
/**
* Converts error code from validatePassword to an array of messages describing the errors with
* important message comes first. The messages are concatenated with a space in between.
* Please make sure each message ends with a period.
*
* @param errorCode the code returned by {@link #validatePassword(byte[]) validatePassword}
*/
public List<String> convertErrorCodeToMessages(Context context, int errorCode) {
return mIsPin ? convertPinErrorCodeToMessages(context, errorCode) :
convertPasswordErrorCodeToMessages(context, errorCode);
}
private int validatePassword(byte[] password) {
int errorCode = NO_ERROR;
if (password.length < MIN_LENGTH) {
errorCode |= TOO_SHORT;
}
// Allow non-control Latin-1 characters only.
for (int i = 0; i < password.length; i++) {
char c = (char) password[i];
if (c < 32 || c > 127) {
errorCode |= CONTAINS_INVALID_CHARACTERS;
break;
}
}
return errorCode;
}
private int translateSettingsToSuwError(int error) {
int output = 0;
if ((error & CONTAINS_NON_DIGITS) > 0) {
LOG.v("CONTAINS_NON_DIGITS");
output |= ValidateLockFlags.INVALID_BAD_SYMBOLS;
}
if ((error & CONTAINS_INVALID_CHARACTERS) > 0) {
LOG.v("INVALID_CHAR");
output |= ValidateLockFlags.INVALID_BAD_SYMBOLS;
}
if ((error & TOO_SHORT) > 0) {
LOG.v("TOO_SHORT");
output |= ValidateLockFlags.INVALID_LENGTH;
}
if ((error & CONTAINS_SEQUENTIAL_DIGITS) > 0) {
LOG.v("SEQUENTIAL_DIGITS");
output |= ValidateLockFlags.INVALID_LACKS_COMPLEXITY;
}
return output;
}
private int validatePin(byte[] pin) {
int errorCode = NO_ERROR;
PasswordMetrics metrics = PasswordMetrics.computeForPassword(pin);
int passwordQuality = getPasswordQuality();
if (metrics.length < MIN_LENGTH) {
errorCode |= TOO_SHORT;
}
if (metrics.letters > 0 || metrics.symbols > 0) {
errorCode |= CONTAINS_NON_DIGITS;
}
if (passwordQuality == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX) {
// Check for repeated characters or sequences (e.g. '1234', '0000', '2468')
int sequence = PasswordMetrics.maxLengthSequence(pin);
if (sequence > PasswordMetrics.MAX_ALLOWED_SEQUENCE) {
errorCode |= CONTAINS_SEQUENTIAL_DIGITS;
}
}
return errorCode;
}
private List<String> convertPasswordErrorCodeToMessages(Context context, int errorCode) {
List<String> messages = new LinkedList<>();
if ((errorCode & CONTAINS_INVALID_CHARACTERS) > 0) {
messages.add(context.getString(R.string.lockpassword_illegal_character));
}
if ((errorCode & TOO_SHORT) > 0) {
messages.add(context.getString(R.string.lockpassword_password_too_short, MIN_LENGTH));
}
return messages;
}
private List<String> convertPinErrorCodeToMessages(Context context, int errorCode) {
List<String> messages = new LinkedList<>();
if ((errorCode & CONTAINS_NON_DIGITS) > 0) {
messages.add(context.getString(R.string.lockpassword_pin_contains_non_digits));
}
if ((errorCode & CONTAINS_SEQUENTIAL_DIGITS) > 0) {
messages.add(context.getString(R.string.lockpassword_pin_no_sequential_digits));
}
if ((errorCode & TOO_SHORT) > 0) {
messages.add(context.getString(R.string.lockpin_invalid_pin));
}
return messages;
}
/**
* Zero out credentials and force garbage collection to remove any remnants of user password
* shards from memory. Should be used in onDestroy for any LockscreenCredential fields.
*
* @param credentials the credentials to zero out, can be null
**/
public static void zeroizeCredentials(LockscreenCredential... credentials) {
for (LockscreenCredential credential : credentials) {
if (credential != null) {
credential.zeroize();
}
}
System.gc();
System.runFinalization();
System.gc();
}
}