1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.car.settings.security; 18 19 import android.app.admin.DevicePolicyManager; 20 import android.app.admin.PasswordMetrics; 21 import android.content.Context; 22 23 import com.android.car.settings.R; 24 import com.android.car.settings.common.Logger; 25 import com.android.car.setupwizardlib.InitialLockSetupConstants.ValidateLockFlags; 26 27 import java.util.LinkedList; 28 import java.util.List; 29 30 /** 31 * Helper used by ChooseLockPinPasswordFragment 32 */ 33 public class PasswordHelper { 34 public static final String EXTRA_CURRENT_SCREEN_LOCK = "extra_current_screen_lock"; 35 /** 36 * Required minimum length of PIN or password. 37 */ 38 public static final int MIN_LENGTH = 4; 39 // Error code returned from validate(String). 40 static final int NO_ERROR = 0; 41 static final int CONTAINS_INVALID_CHARACTERS = 1; 42 static final int TOO_SHORT = 1 << 1; 43 static final int CONTAINS_NON_DIGITS = 1 << 2; 44 static final int CONTAINS_SEQUENTIAL_DIGITS = 1 << 3; 45 private static final Logger LOG = new Logger(PasswordHelper.class); 46 private final boolean mIsPin; 47 PasswordHelper(boolean isPin)48 public PasswordHelper(boolean isPin) { 49 mIsPin = isPin; 50 } 51 52 /** 53 * Returns one of the password quality values defined in {@link DevicePolicyManager}, such 54 * as NUMERIC, ALPHANUMERIC etc. 55 */ getPasswordQuality()56 public int getPasswordQuality() { 57 return mIsPin ? DevicePolicyManager.PASSWORD_QUALITY_NUMERIC : 58 DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC; 59 } 60 61 /** 62 * Validates PIN/Password and returns the validation result. 63 * 64 * @param password the raw bytes for the password the user typed in 65 * @return the error code which should be non-zero where there is error. Otherwise 66 * {@link #NO_ERROR} should be returned. 67 */ validate(byte[] password)68 public int validate(byte[] password) { 69 return mIsPin ? validatePin(password) : validatePassword(password); 70 } 71 72 /** 73 * Validates PIN/Password using the setup wizard {@link ValidateLockFlags}. 74 * 75 * @param password The password to validate. 76 * @return The error code where 0 is no error. 77 */ validateSetupWizard(byte[] password)78 public int validateSetupWizard(byte[] password) { 79 return mIsPin ? translateSettingsToSuwError(validatePin(password)) 80 : translateSettingsToSuwError(validatePassword(password)); 81 } 82 83 /** 84 * Converts error code from validatePassword to an array of messages describing the errors with 85 * important message comes first. The messages are concatenated with a space in between. 86 * Please make sure each message ends with a period. 87 * 88 * @param errorCode the code returned by {@link #validatePassword(byte[]) validatePassword} 89 */ convertErrorCodeToMessages(Context context, int errorCode)90 public List<String> convertErrorCodeToMessages(Context context, int errorCode) { 91 return mIsPin ? convertPinErrorCodeToMessages(context, errorCode) : 92 convertPasswordErrorCodeToMessages(context, errorCode); 93 } 94 validatePassword(byte[] password)95 private int validatePassword(byte[] password) { 96 int errorCode = NO_ERROR; 97 98 if (password.length < MIN_LENGTH) { 99 errorCode |= TOO_SHORT; 100 } 101 102 // Allow non-control Latin-1 characters only. 103 for (int i = 0; i < password.length; i++) { 104 char c = (char) password[i]; 105 if (c < 32 || c > 127) { 106 errorCode |= CONTAINS_INVALID_CHARACTERS; 107 break; 108 } 109 } 110 111 return errorCode; 112 } 113 translateSettingsToSuwError(int error)114 private int translateSettingsToSuwError(int error) { 115 int output = 0; 116 if ((error & CONTAINS_NON_DIGITS) > 0) { 117 LOG.v("CONTAINS_NON_DIGITS"); 118 output |= ValidateLockFlags.INVALID_BAD_SYMBOLS; 119 } 120 if ((error & CONTAINS_INVALID_CHARACTERS) > 0) { 121 LOG.v("INVALID_CHAR"); 122 output |= ValidateLockFlags.INVALID_BAD_SYMBOLS; 123 } 124 if ((error & TOO_SHORT) > 0) { 125 LOG.v("TOO_SHORT"); 126 output |= ValidateLockFlags.INVALID_LENGTH; 127 } 128 if ((error & CONTAINS_SEQUENTIAL_DIGITS) > 0) { 129 LOG.v("SEQUENTIAL_DIGITS"); 130 output |= ValidateLockFlags.INVALID_LACKS_COMPLEXITY; 131 } 132 return output; 133 } 134 validatePin(byte[] pin)135 private int validatePin(byte[] pin) { 136 int errorCode = NO_ERROR; 137 PasswordMetrics metrics = PasswordMetrics.computeForPassword(pin); 138 int passwordQuality = getPasswordQuality(); 139 140 if (metrics.length < MIN_LENGTH) { 141 errorCode |= TOO_SHORT; 142 } 143 144 if (metrics.letters > 0 || metrics.symbols > 0) { 145 errorCode |= CONTAINS_NON_DIGITS; 146 } 147 148 if (passwordQuality == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX) { 149 // Check for repeated characters or sequences (e.g. '1234', '0000', '2468') 150 int sequence = PasswordMetrics.maxLengthSequence(pin); 151 if (sequence > PasswordMetrics.MAX_ALLOWED_SEQUENCE) { 152 errorCode |= CONTAINS_SEQUENTIAL_DIGITS; 153 } 154 } 155 156 return errorCode; 157 } 158 convertPasswordErrorCodeToMessages(Context context, int errorCode)159 private List<String> convertPasswordErrorCodeToMessages(Context context, int errorCode) { 160 List<String> messages = new LinkedList<>(); 161 162 if ((errorCode & CONTAINS_INVALID_CHARACTERS) > 0) { 163 messages.add(context.getString(R.string.lockpassword_illegal_character)); 164 } 165 166 if ((errorCode & TOO_SHORT) > 0) { 167 messages.add(context.getString(R.string.lockpassword_password_too_short, MIN_LENGTH)); 168 } 169 170 return messages; 171 } 172 convertPinErrorCodeToMessages(Context context, int errorCode)173 private List<String> convertPinErrorCodeToMessages(Context context, int errorCode) { 174 List<String> messages = new LinkedList<>(); 175 176 if ((errorCode & CONTAINS_NON_DIGITS) > 0) { 177 messages.add(context.getString(R.string.lockpassword_pin_contains_non_digits)); 178 } 179 180 if ((errorCode & CONTAINS_SEQUENTIAL_DIGITS) > 0) { 181 messages.add(context.getString(R.string.lockpassword_pin_no_sequential_digits)); 182 } 183 184 if ((errorCode & TOO_SHORT) > 0) { 185 messages.add(context.getString(R.string.lockpin_invalid_pin)); 186 } 187 188 return messages; 189 } 190 } 191