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 static com.android.internal.widget.PasswordValidationError.CONTAINS_INVALID_CHARACTERS; 20 import static com.android.internal.widget.PasswordValidationError.CONTAINS_SEQUENCE; 21 import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_DIGITS; 22 import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_LETTERS; 23 import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_LOWER_CASE; 24 import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_NON_DIGITS; 25 import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_NON_LETTER; 26 import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_SYMBOLS; 27 import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_UPPER_CASE; 28 import static com.android.internal.widget.PasswordValidationError.RECENTLY_USED; 29 import static com.android.internal.widget.PasswordValidationError.TOO_LONG; 30 import static com.android.internal.widget.PasswordValidationError.TOO_SHORT; 31 32 import android.annotation.UserIdInt; 33 import android.app.admin.DevicePolicyManager; 34 import android.app.admin.PasswordMetrics; 35 import android.content.Context; 36 37 import androidx.annotation.VisibleForTesting; 38 39 import com.android.car.settings.R; 40 import com.android.car.settings.common.Logger; 41 import com.android.internal.widget.LockPatternUtils; 42 import com.android.internal.widget.LockscreenCredential; 43 import com.android.internal.widget.PasswordValidationError; 44 import com.android.settingslib.utils.StringUtil; 45 46 import java.util.ArrayList; 47 import java.util.Collections; 48 import java.util.List; 49 50 /** 51 * Helper used by ChooseLockPinPasswordFragment 52 * Much of the logic is taken from {@link com.android.settings.password.ChooseLockPassword} 53 */ 54 public class PasswordHelper { 55 public static final String EXTRA_CURRENT_SCREEN_LOCK = "extra_current_screen_lock"; 56 public static final String EXTRA_CURRENT_PASSWORD_QUALITY = "extra_current_password_quality"; 57 58 private static final Logger LOG = new Logger(PasswordHelper.class); 59 60 private final Context mContext; 61 private final LockPatternUtils mLockPatternUtils; 62 private final PasswordMetrics mMinMetrics; 63 private List<PasswordValidationError> mValidationErrors; 64 private boolean mIsPin; 65 private boolean mIsPattern; 66 private byte[] mPasswordHistoryHashFactor; 67 68 @UserIdInt 69 private final int mUserId; 70 71 @DevicePolicyManager.PasswordComplexity 72 private final int mMinComplexity; 73 PasswordHelper(Context context, @UserIdInt int userId)74 public PasswordHelper(Context context, @UserIdInt int userId) { 75 mContext = context; 76 mUserId = userId; 77 mLockPatternUtils = new LockPatternUtils(context); 78 mMinMetrics = mLockPatternUtils.getRequestedPasswordMetrics( 79 mUserId, /* deviceWideOnly= */ false); 80 mMinComplexity = mLockPatternUtils.getRequestedPasswordComplexity( 81 mUserId, /* deviceWideOnly= */ false); 82 } 83 84 @VisibleForTesting PasswordHelper(Context context, @UserIdInt int userId, LockPatternUtils lockPatternUtils, PasswordMetrics minMetrics, @DevicePolicyManager.PasswordComplexity int minComplexity)85 PasswordHelper(Context context, @UserIdInt int userId, LockPatternUtils lockPatternUtils, 86 PasswordMetrics minMetrics, @DevicePolicyManager.PasswordComplexity int minComplexity) { 87 mContext = context; 88 mUserId = userId; 89 mLockPatternUtils = lockPatternUtils; 90 mMinMetrics = minMetrics; 91 mMinComplexity = minComplexity; 92 } 93 94 /** 95 * Validates a proposed new lockscreen credential. Does not check it against the password 96 * history, but does all other types of validation such as length, allowed characters, etc. 97 * {@link #getCredentialValidationErrorMessages()} can be called afterwards to retrieve the 98 * error message(s). 99 * 100 * @param credential the proposed new lockscreen credential 101 * @return whether the new credential is valid 102 */ validateCredential(LockscreenCredential credential)103 public boolean validateCredential(LockscreenCredential credential) { 104 mValidationErrors = PasswordMetrics.validateCredential(mMinMetrics, mMinComplexity, 105 credential); 106 mIsPin = credential.isPin(); 107 mIsPattern = credential.isPattern(); 108 return mValidationErrors.isEmpty(); 109 } 110 111 /** 112 * Validates a proposed new lockscreen credential. Does the full validation including checking 113 * against the password history. {@link #getCredentialValidationErrorMessages()} can be called 114 * afterwards to retrieve the error message(s). 115 * 116 * @param enteredCredential the proposed new lockscreen credential 117 * @param existingCredential the current lockscreen credential 118 * @return whether the new credential is valid 119 */ validateCredential(LockscreenCredential enteredCredential, LockscreenCredential existingCredential)120 public boolean validateCredential(LockscreenCredential enteredCredential, 121 LockscreenCredential existingCredential) { 122 if (validateCredential(enteredCredential) 123 && mLockPatternUtils.checkPasswordHistory(enteredCredential.getCredential(), 124 getPasswordHistoryHashFactor(existingCredential), mUserId)) { 125 mValidationErrors = 126 Collections.singletonList(new PasswordValidationError(RECENTLY_USED)); 127 } 128 return mValidationErrors.isEmpty(); 129 } 130 131 /** 132 * Lazily computes and returns the history hash factor of the user id of the current process 133 * {@code mUserId}, used for password history check. 134 */ getPasswordHistoryHashFactor(LockscreenCredential credential)135 private byte[] getPasswordHistoryHashFactor(LockscreenCredential credential) { 136 if (mPasswordHistoryHashFactor == null) { 137 mPasswordHistoryHashFactor = mLockPatternUtils.getPasswordHistoryHashFactor( 138 credential != null ? credential : LockscreenCredential.createNone(), mUserId); 139 } 140 return mPasswordHistoryHashFactor; 141 } 142 143 /** 144 * Returns a message describing any errors of the last call to {@link 145 * #validateCredential(LockscreenCredential)} or {@link 146 * #validateCredential(LockscreenCredential, LockscreenCredential)}. 147 * Returns an empty string if there were no errors. 148 */ getCredentialValidationErrorMessages()149 public String getCredentialValidationErrorMessages() { 150 List<String> messages = new ArrayList<>(); 151 for (PasswordValidationError error : mValidationErrors) { 152 switch (error.errorCode) { 153 case CONTAINS_INVALID_CHARACTERS: 154 messages.add(mContext.getString(R.string.lockpassword_illegal_character)); 155 break; 156 case NOT_ENOUGH_UPPER_CASE: 157 messages.add(StringUtil.getIcuPluralsString(mContext, error.requirement, 158 R.string.lockpassword_password_requires_uppercase)); 159 break; 160 case NOT_ENOUGH_LOWER_CASE: 161 messages.add(StringUtil.getIcuPluralsString(mContext, error.requirement, 162 R.string.lockpassword_password_requires_lowercase)); 163 break; 164 case NOT_ENOUGH_LETTERS: 165 messages.add(StringUtil.getIcuPluralsString(mContext, error.requirement, 166 R.string.lockpassword_password_requires_letters)); 167 break; 168 case NOT_ENOUGH_DIGITS: 169 messages.add(StringUtil.getIcuPluralsString(mContext, error.requirement, 170 R.string.lockpassword_password_requires_numeric)); 171 break; 172 case NOT_ENOUGH_SYMBOLS: 173 messages.add(StringUtil.getIcuPluralsString(mContext, error.requirement, 174 R.string.lockpassword_password_requires_symbols)); 175 break; 176 case NOT_ENOUGH_NON_LETTER: 177 messages.add(StringUtil.getIcuPluralsString(mContext, error.requirement, 178 R.string.lockpassword_password_requires_nonletter)); 179 break; 180 case NOT_ENOUGH_NON_DIGITS: 181 messages.add(StringUtil.getIcuPluralsString(mContext, error.requirement, 182 R.string.lockpassword_password_requires_nonnumerical)); 183 break; 184 case TOO_SHORT: 185 messages.add(StringUtil.getIcuPluralsString(mContext, error.requirement, 186 mIsPin 187 ? R.string.lockpassword_pin_too_short 188 : mIsPattern 189 ? R.string.lockpattern_recording_incorrect_too_short 190 : R.string.lockpassword_password_too_short)); 191 break; 192 case TOO_LONG: 193 messages.add(StringUtil.getIcuPluralsString(mContext, error.requirement + 1, 194 mIsPin 195 ? R.string.lockpassword_pin_too_long 196 : R.string.lockpassword_password_too_long)); 197 break; 198 case CONTAINS_SEQUENCE: 199 messages.add(mContext.getString( 200 R.string.lockpassword_pin_no_sequential_digits)); 201 break; 202 case RECENTLY_USED: 203 messages.add(mContext.getString(mIsPin 204 ? R.string.lockpassword_pin_recently_used 205 : R.string.lockpassword_password_recently_used)); 206 break; 207 default: 208 LOG.wtf("unknown error validating password: " + error); 209 } 210 } 211 if (messages.isEmpty() && !mValidationErrors.isEmpty()) { 212 // All errors were unknown, so fall back to the default message. If you see this message 213 // in the UI, something needs to be added to the switch statement above! 214 messages.add(mContext.getString(R.string.lockpassword_invalid_password)); 215 } 216 return String.join("\n", messages); 217 } 218 219 /** 220 * Zero out credentials and force garbage collection to remove any remnants of user password 221 * shards from memory. Should be used in onDestroy for any LockscreenCredential fields. 222 * 223 * @param credentials the credentials to zero out, can be null 224 **/ zeroizeCredentials(LockscreenCredential... credentials)225 public static void zeroizeCredentials(LockscreenCredential... credentials) { 226 for (LockscreenCredential credential : credentials) { 227 if (credential != null) { 228 credential.zeroize(); 229 } 230 } 231 232 System.gc(); 233 System.runFinalization(); 234 System.gc(); 235 } 236 } 237