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