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