• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.car.setupwizardlib.InitialLockSetupConstants.LockTypes;
42 import com.android.internal.widget.LockPatternUtils;
43 import com.android.internal.widget.LockscreenCredential;
44 import com.android.internal.widget.PasswordValidationError;
45 import com.android.settingslib.utils.StringUtil;
46 
47 import java.util.ArrayList;
48 import java.util.Collections;
49 import java.util.List;
50 
51 /**
52  * Helper used by ChooseLockPinPasswordFragment
53  * Much of the logic is taken from {@link com.android.settings.password.ChooseLockPassword}
54  */
55 public class PasswordHelper {
56     public static final String EXTRA_CURRENT_SCREEN_LOCK = "extra_current_screen_lock";
57     public static final String EXTRA_CURRENT_PASSWORD_QUALITY = "extra_current_password_quality";
58 
59     // String to be returned to when there is no password complexity validation error.
60     @VisibleForTesting
61     static final String NO_ERROR_MESSAGE = "";
62 
63     // Error code returned from validateSetupWizard(byte[] password).
64     static final int NO_ERROR = 0;
65     static final int ERROR_CODE = 1;
66     private static final Logger LOG = new Logger(PasswordHelper.class);
67 
68     private final Context mContext;
69     private final boolean mIsPin;
70     private final LockPatternUtils mLockPatternUtils;
71     private final PasswordMetrics mMinMetrics;
72     private List<PasswordValidationError> mValidationErrors;
73     private byte[] mPasswordHistoryHashFactor;
74 
75     @UserIdInt
76     private final int mUserId;
77 
78     @DevicePolicyManager.PasswordComplexity
79     private final int mMinComplexity;
80 
PasswordHelper(Context context, boolean isPin, @UserIdInt int userId)81     public PasswordHelper(Context context, boolean isPin, @UserIdInt int userId) {
82         mContext = context;
83         mIsPin = isPin;
84         mUserId = userId;
85         mLockPatternUtils = new LockPatternUtils(context);
86         mMinMetrics = mLockPatternUtils.getRequestedPasswordMetrics(
87                 mUserId, /* deviceWideOnly= */ false);
88         mMinComplexity = mLockPatternUtils.getRequestedPasswordComplexity(
89                 mUserId, /* deviceWideOnly= */ false);
90     }
91 
92     @VisibleForTesting
PasswordHelper(Context context, boolean isPin, @UserIdInt int userId, LockPatternUtils lockPatternUtils, PasswordMetrics minMetrics, @DevicePolicyManager.PasswordComplexity int minComplexity)93     PasswordHelper(Context context, boolean isPin, @UserIdInt int userId,
94             LockPatternUtils lockPatternUtils, PasswordMetrics minMetrics,
95             @DevicePolicyManager.PasswordComplexity int minComplexity) {
96         mContext = context;
97         mIsPin = isPin;
98         mUserId = userId;
99         mLockPatternUtils = lockPatternUtils;
100         mMinMetrics = minMetrics;
101         mMinComplexity = minComplexity;
102     }
103 
104     /**
105      * Validates PIN/Password, updates mValidationErrors, and then returns the validation result.
106      *
107      * @param password password or PIN entered by the user in bytes.
108      * @return The ERROR_CODE if there is any validation error, or NO_ERROR otherwise.
109      */
validateSetupWizard(byte[] password)110     public int validateSetupWizard(byte[] password) {
111         mValidationErrors = PasswordMetrics.validatePassword(/* adminMetrics */ mMinMetrics,
112                 /* minComplexity */ mMinComplexity, /* isPin */ mIsPin, /* password */ password);
113         return mValidationErrors.isEmpty() ? NO_ERROR : ERROR_CODE;
114     }
115 
116     /**
117      * Validates the PIN/Password/Pattern and return the combined error message associated with the
118      * user input if exists, or return {@code NO_ERROR_MESSAGE} otherwise.
119      *
120      * If the lock type is PASSWORD or PIN, update mValidationErrors.
121      *
122      * @param credentialBytes password/PIN/pattern entered by the user in bytes.
123      * @return The error message to display to users describing all credential validation errors,
124      * where an empty String NO_ERROR_MSG when there is no error.
125      */
validateSetupWizardAndReturnError(@ockTypes int lockType, byte[] credentialBytes)126     public String validateSetupWizardAndReturnError(@LockTypes int lockType,
127             byte[] credentialBytes) {
128         if (lockType == LockTypes.PATTERN) {
129             return validatePatternAndReturnError(credentialBytes);
130         } else if (lockType == LockTypes.PASSWORD || lockType == LockTypes.PIN) {
131             mValidationErrors = PasswordMetrics.validatePassword(/* adminMetrics */ mMinMetrics,
132                     /* minComplexity */ mMinComplexity, /* isPin */ lockType == LockTypes.PIN,
133                     /* password */ credentialBytes);
134             if (!mValidationErrors.isEmpty()) {
135                 List<String> messages = convertErrorCodeToMessages();
136                 if (!messages.isEmpty()) {
137                     return getCombinedErrorMessage(messages);
138                 }
139                 LOG.wtf("A validation error was returned, but no matching error message was found");
140                 return mContext.getString(R.string.lockpassword_invalid_password);
141             }
142             return NO_ERROR_MESSAGE;
143         } else {
144             // the only accepted lock types are PATTERN, PASSWORD, and PIN for validation.
145             LOG.wtf("An unknown lock type was pass in");
146             return mContext.getString(R.string.locktype_unavailable);
147         }
148     }
149 
validatePatternAndReturnError(byte[] pattern)150     private String validatePatternAndReturnError(byte[] pattern) {
151         if (pattern.length < LockPatternUtils.MIN_LOCK_PATTERN_SIZE) {
152             return mContext.getString(R.string.lockpattern_recording_incorrect_too_short);
153         }
154         return NO_ERROR_MESSAGE;
155     }
156 
157     /**
158      * Combines all error messages into a String displayable to the user.
159      *
160      * @param messages the return value of convertErrorCodeToMessages
161      * @return new line separated String with each new line describing the error
162      */
getCombinedErrorMessage(List<String> messages)163     String getCombinedErrorMessage(List<String> messages) {
164         return String.join("\n", messages);
165     }
166 
167     /**
168      * Validates PIN/Password and returns the validation result and updates mValidationErrors
169      * and checks whether the password has been reused.
170      *
171      * @param enteredCredential credential the user typed in.
172      * @param existingCredential existing credential the user previously set.
173      * @return whether password satisfies all the requirements.
174      */
validate(LockscreenCredential enteredCredential, LockscreenCredential existingCredential)175     public boolean validate(LockscreenCredential enteredCredential,
176             LockscreenCredential existingCredential) {
177         byte[] password = enteredCredential.getCredential();
178         mValidationErrors =
179                 PasswordMetrics.validatePassword(mMinMetrics, mMinComplexity, mIsPin, password);
180         if (mValidationErrors.isEmpty() && mLockPatternUtils.checkPasswordHistory(
181                 password, getPasswordHistoryHashFactor(existingCredential), mUserId)) {
182             mValidationErrors =
183                     Collections.singletonList(new PasswordValidationError(RECENTLY_USED));
184         }
185 
186         return mValidationErrors.isEmpty();
187     }
188 
189     /**
190      * Lazily computes and returns the history hash factor of the user id of the current process
191      * {@code mUserId}, used for password history check.
192      */
getPasswordHistoryHashFactor(LockscreenCredential credential)193     private byte[] getPasswordHistoryHashFactor(LockscreenCredential credential) {
194         if (mPasswordHistoryHashFactor == null) {
195             mPasswordHistoryHashFactor = mLockPatternUtils.getPasswordHistoryHashFactor(
196                     credential != null ? credential : LockscreenCredential.createNone(), mUserId);
197         }
198         return mPasswordHistoryHashFactor;
199     }
200 
201     /**
202      * Returns an array of messages describing any errors of the last
203      * {@link #validate(LockscreenCredential)} call, important messages come first.
204      */
convertErrorCodeToMessages()205     public List<String> convertErrorCodeToMessages() {
206         List<String> messages = new ArrayList<>();
207         for (PasswordValidationError error : mValidationErrors) {
208             switch (error.errorCode) {
209                 case CONTAINS_INVALID_CHARACTERS:
210                     messages.add(mContext.getString(R.string.lockpassword_illegal_character));
211                     break;
212                 case NOT_ENOUGH_UPPER_CASE:
213                     messages.add(StringUtil.getIcuPluralsString(mContext, error.requirement,
214                             R.string.lockpassword_password_requires_uppercase));
215                     break;
216                 case NOT_ENOUGH_LOWER_CASE:
217                     messages.add(StringUtil.getIcuPluralsString(mContext, error.requirement,
218                             R.string.lockpassword_password_requires_lowercase));
219                     break;
220                 case NOT_ENOUGH_LETTERS:
221                     messages.add(StringUtil.getIcuPluralsString(mContext, error.requirement,
222                             R.string.lockpassword_password_requires_letters));
223                     break;
224                 case NOT_ENOUGH_DIGITS:
225                     messages.add(StringUtil.getIcuPluralsString(mContext, error.requirement,
226                             R.string.lockpassword_password_requires_numeric));
227                     break;
228                 case NOT_ENOUGH_SYMBOLS:
229                     messages.add(StringUtil.getIcuPluralsString(mContext, error.requirement,
230                             R.string.lockpassword_password_requires_symbols));
231                     break;
232                 case NOT_ENOUGH_NON_LETTER:
233                     messages.add(StringUtil.getIcuPluralsString(mContext, error.requirement,
234                             R.string.lockpassword_password_requires_nonletter));
235                     break;
236                 case NOT_ENOUGH_NON_DIGITS:
237                     messages.add(StringUtil.getIcuPluralsString(mContext, error.requirement,
238                             R.string.lockpassword_password_requires_nonnumerical));
239                     break;
240                 case TOO_SHORT:
241                     messages.add(StringUtil.getIcuPluralsString(mContext, error.requirement,
242                             mIsPin
243                                     ? R.string.lockpassword_pin_too_short
244                                     : R.string.lockpassword_password_too_short));
245                     break;
246                 case TOO_LONG:
247                     messages.add(StringUtil.getIcuPluralsString(mContext, error.requirement + 1,
248                             mIsPin
249                                     ? R.string.lockpassword_pin_too_long
250                                     : R.string.lockpassword_password_too_long));
251                     break;
252                 case CONTAINS_SEQUENCE:
253                     messages.add(mContext.getString(
254                             R.string.lockpassword_pin_no_sequential_digits));
255                     break;
256                 case RECENTLY_USED:
257                     messages.add(mContext.getString(mIsPin
258                             ? R.string.lockpassword_pin_recently_used
259                             : R.string.lockpassword_password_recently_used));
260                     break;
261                 default:
262                     LOG.wtf("unknown error validating password: " + error);
263             }
264         }
265         return messages;
266     }
267 
268     /**
269      * Zero out credentials and force garbage collection to remove any remnants of user password
270      * shards from memory. Should be used in onDestroy for any LockscreenCredential fields.
271      *
272      * @param credentials the credentials to zero out, can be null
273      **/
zeroizeCredentials(LockscreenCredential... credentials)274     public static void zeroizeCredentials(LockscreenCredential... credentials) {
275         for (LockscreenCredential credential : credentials) {
276             if (credential != null) {
277                 credential.zeroize();
278             }
279         }
280 
281         System.gc();
282         System.runFinalization();
283         System.gc();
284     }
285 }
286