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 import com.android.internal.widget.LockscreenCredential; 27 28 import java.util.LinkedList; 29 import java.util.List; 30 31 /** 32 * Helper used by ChooseLockPinPasswordFragment 33 */ 34 public class PasswordHelper { 35 public static final String EXTRA_CURRENT_SCREEN_LOCK = "extra_current_screen_lock"; 36 public static final String EXTRA_CURRENT_PASSWORD_QUALITY = "extra_current_password_quality"; 37 /** 38 * Required minimum length of PIN or password. 39 */ 40 public static final int MIN_LENGTH = 4; 41 // Error code returned from validate(String). 42 static final int NO_ERROR = 0; 43 static final int CONTAINS_INVALID_CHARACTERS = 1; 44 static final int TOO_SHORT = 1 << 1; 45 static final int CONTAINS_NON_DIGITS = 1 << 2; 46 static final int CONTAINS_SEQUENTIAL_DIGITS = 1 << 3; 47 private static final Logger LOG = new Logger(PasswordHelper.class); 48 private final boolean mIsPin; 49 PasswordHelper(boolean isPin)50 public PasswordHelper(boolean isPin) { 51 mIsPin = isPin; 52 } 53 54 /** 55 * Returns one of the password quality values defined in {@link DevicePolicyManager}, such 56 * as NUMERIC, ALPHANUMERIC etc. 57 */ getPasswordQuality()58 public int getPasswordQuality() { 59 return mIsPin ? DevicePolicyManager.PASSWORD_QUALITY_NUMERIC : 60 DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC; 61 } 62 63 /** 64 * Validates PIN/Password and returns the validation result. 65 * 66 * @param password the raw bytes for the password the user typed in 67 * @return the error code which should be non-zero where there is error. Otherwise 68 * {@link #NO_ERROR} should be returned. 69 */ validate(LockscreenCredential password)70 public int validate(LockscreenCredential password) { 71 return mIsPin ? validatePin(password.getCredential()) 72 : validatePassword(password.getCredential()); 73 } 74 75 /** 76 * Validates PIN/Password using the setup wizard {@link ValidateLockFlags}. 77 * 78 * @param password The password to validate. 79 * @return The error code where 0 is no error. 80 */ validateSetupWizard(byte[] password)81 public int validateSetupWizard(byte[] password) { 82 return mIsPin ? translateSettingsToSuwError(validatePin(password)) 83 : translateSettingsToSuwError(validatePassword(password)); 84 } 85 86 /** 87 * Converts error code from validatePassword to an array of messages describing the errors with 88 * important message comes first. The messages are concatenated with a space in between. 89 * Please make sure each message ends with a period. 90 * 91 * @param errorCode the code returned by {@link #validatePassword(byte[]) validatePassword} 92 */ convertErrorCodeToMessages(Context context, int errorCode)93 public List<String> convertErrorCodeToMessages(Context context, int errorCode) { 94 return mIsPin ? convertPinErrorCodeToMessages(context, errorCode) : 95 convertPasswordErrorCodeToMessages(context, errorCode); 96 } 97 validatePassword(byte[] password)98 private int validatePassword(byte[] password) { 99 int errorCode = NO_ERROR; 100 101 if (password.length < MIN_LENGTH) { 102 errorCode |= TOO_SHORT; 103 } 104 105 // Allow non-control Latin-1 characters only. 106 for (int i = 0; i < password.length; i++) { 107 char c = (char) password[i]; 108 if (c < 32 || c > 127) { 109 errorCode |= CONTAINS_INVALID_CHARACTERS; 110 break; 111 } 112 } 113 114 return errorCode; 115 } 116 translateSettingsToSuwError(int error)117 private int translateSettingsToSuwError(int error) { 118 int output = 0; 119 if ((error & CONTAINS_NON_DIGITS) > 0) { 120 LOG.v("CONTAINS_NON_DIGITS"); 121 output |= ValidateLockFlags.INVALID_BAD_SYMBOLS; 122 } 123 if ((error & CONTAINS_INVALID_CHARACTERS) > 0) { 124 LOG.v("INVALID_CHAR"); 125 output |= ValidateLockFlags.INVALID_BAD_SYMBOLS; 126 } 127 if ((error & TOO_SHORT) > 0) { 128 LOG.v("TOO_SHORT"); 129 output |= ValidateLockFlags.INVALID_LENGTH; 130 } 131 if ((error & CONTAINS_SEQUENTIAL_DIGITS) > 0) { 132 LOG.v("SEQUENTIAL_DIGITS"); 133 output |= ValidateLockFlags.INVALID_LACKS_COMPLEXITY; 134 } 135 return output; 136 } 137 validatePin(byte[] pin)138 private int validatePin(byte[] pin) { 139 int errorCode = NO_ERROR; 140 PasswordMetrics metrics = PasswordMetrics.computeForPasswordOrPin(pin, /* isPin */ true); 141 int passwordQuality = getPasswordQuality(); 142 143 if (metrics.length < MIN_LENGTH) { 144 errorCode |= TOO_SHORT; 145 } 146 147 if (metrics.letters > 0 || metrics.symbols > 0) { 148 errorCode |= CONTAINS_NON_DIGITS; 149 } 150 151 if (passwordQuality == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX) { 152 // Check for repeated characters or sequences (e.g. '1234', '0000', '2468') 153 int sequence = PasswordMetrics.maxLengthSequence(pin); 154 if (sequence > PasswordMetrics.MAX_ALLOWED_SEQUENCE) { 155 errorCode |= CONTAINS_SEQUENTIAL_DIGITS; 156 } 157 } 158 159 return errorCode; 160 } 161 convertPasswordErrorCodeToMessages(Context context, int errorCode)162 private List<String> convertPasswordErrorCodeToMessages(Context context, int errorCode) { 163 List<String> messages = new LinkedList<>(); 164 165 if ((errorCode & CONTAINS_INVALID_CHARACTERS) > 0) { 166 messages.add(context.getString(R.string.lockpassword_illegal_character)); 167 } 168 169 if ((errorCode & TOO_SHORT) > 0) { 170 messages.add(context.getString(R.string.lockpassword_password_too_short, MIN_LENGTH)); 171 } 172 173 return messages; 174 } 175 convertPinErrorCodeToMessages(Context context, int errorCode)176 private List<String> convertPinErrorCodeToMessages(Context context, int errorCode) { 177 List<String> messages = new LinkedList<>(); 178 179 if ((errorCode & CONTAINS_NON_DIGITS) > 0) { 180 messages.add(context.getString(R.string.lockpassword_pin_contains_non_digits)); 181 } 182 183 if ((errorCode & CONTAINS_SEQUENTIAL_DIGITS) > 0) { 184 messages.add(context.getString(R.string.lockpassword_pin_no_sequential_digits)); 185 } 186 187 if ((errorCode & TOO_SHORT) > 0) { 188 messages.add(context.getString(R.string.lockpin_invalid_pin)); 189 } 190 191 return messages; 192 } 193 194 /** 195 * Zero out credentials and force garbage collection to remove any remnants of user password 196 * shards from memory. Should be used in onDestroy for any LockscreenCredential fields. 197 * 198 * @param credentials the credentials to zero out, can be null 199 **/ zeroizeCredentials(LockscreenCredential... credentials)200 public static void zeroizeCredentials(LockscreenCredential... credentials) { 201 for (LockscreenCredential credential : credentials) { 202 if (credential != null) { 203 credential.zeroize(); 204 } 205 } 206 207 System.gc(); 208 System.runFinalization(); 209 System.gc(); 210 } 211 } 212