1 /*
2  * Copyright (C) 2019 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.internal.widget;
18 
19 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
20 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
21 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN;
22 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
23 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
24 
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.os.Parcel;
28 import android.os.Parcelable;
29 import android.os.storage.StorageManager;
30 import android.text.TextUtils;
31 
32 import com.android.internal.util.Preconditions;
33 
34 import java.util.Arrays;
35 import java.util.List;
36 import java.util.Objects;
37 
38 /**
39  * A class representing a lockscreen credential. It can be either an empty password, a pattern
40  * or a password (or PIN).
41  *
42  * <p> As required by some security certification, the framework tries its best to
43  * remove copies of the lockscreen credential bytes from memory. In this regard, this class
44  * abuses the {@link AutoCloseable} interface for sanitizing memory. This
45  * presents a nice syntax to auto-zeroize memory with the try-with-resource statement:
46  * <pre>
47  * try {LockscreenCredential credential = LockscreenCredential.createPassword(...) {
48  *     // Process the credential in some way
49  * }
50  * </pre>
51  * With this construct, we can garantee that there will be no copies of the password left in
52  * memory when the credential goes out of scope. This should help mitigate certain class of
53  * attacks where the attcker gains read-only access to full device memory (cold boot attack,
54  * unsecured software/hardware memory dumping interfaces such as JTAG).
55  */
56 public class LockscreenCredential implements Parcelable, AutoCloseable {
57 
58     private final int mType;
59     // Stores raw credential bytes, or null if credential has been zeroized. An empty password
60     // is represented as a byte array of length 0.
61     private byte[] mCredential;
62 
63     /**
64      * Private constructor, use static builder methods instead.
65      *
66      * <p> Builder methods should create a private copy of the credential bytes and pass in here.
67      * LockscreenCredential will only store the reference internally without copying. This is to
68      * minimize the number of extra copies introduced.
69      */
LockscreenCredential(int type, byte[] credential)70     private LockscreenCredential(int type, byte[] credential) {
71         Objects.requireNonNull(credential);
72         if (type == CREDENTIAL_TYPE_NONE) {
73             Preconditions.checkArgument(credential.length == 0);
74         } else {
75             // Do not allow constructing a CREDENTIAL_TYPE_PASSWORD_OR_PIN object.
76             Preconditions.checkArgument(type == CREDENTIAL_TYPE_PIN
77                     || type == CREDENTIAL_TYPE_PASSWORD
78                     || type == CREDENTIAL_TYPE_PATTERN);
79             Preconditions.checkArgument(credential.length > 0);
80         }
81         mType = type;
82         mCredential = credential;
83     }
84 
85     /**
86      * Creates a LockscreenCredential object representing empty password.
87      */
createNone()88     public static LockscreenCredential createNone() {
89         return new LockscreenCredential(CREDENTIAL_TYPE_NONE, new byte[0]);
90     }
91 
92     /**
93      * Creates a LockscreenCredential object representing the given pattern.
94      */
createPattern(@onNull List<LockPatternView.Cell> pattern)95     public static LockscreenCredential createPattern(@NonNull List<LockPatternView.Cell> pattern) {
96         return new LockscreenCredential(CREDENTIAL_TYPE_PATTERN,
97                 LockPatternUtils.patternToByteArray(pattern));
98     }
99 
100     /**
101      * Creates a LockscreenCredential object representing the given alphabetic password.
102      */
createPassword(@onNull CharSequence password)103     public static LockscreenCredential createPassword(@NonNull CharSequence password) {
104         return new LockscreenCredential(CREDENTIAL_TYPE_PASSWORD,
105                 charSequenceToByteArray(password));
106     }
107 
108     /**
109      * Creates a LockscreenCredential object representing a managed password for profile with
110      * unified challenge. This credentiall will have type {@code CREDENTIAL_TYPE_PASSWORD} for now.
111      * TODO: consider add a new credential type for this. This can then supersede the
112      * isLockTiedToParent argument in various places in LSS.
113      */
createManagedPassword(@onNull byte[] password)114     public static LockscreenCredential createManagedPassword(@NonNull byte[] password) {
115         return new LockscreenCredential(CREDENTIAL_TYPE_PASSWORD,
116                 Arrays.copyOf(password, password.length));
117     }
118 
119     /**
120      * Creates a LockscreenCredential object representing the given numeric PIN.
121      */
createPin(@onNull CharSequence pin)122     public static LockscreenCredential createPin(@NonNull CharSequence pin) {
123         return new LockscreenCredential(CREDENTIAL_TYPE_PIN,
124                 charSequenceToByteArray(pin));
125     }
126 
127     /**
128      * Creates a LockscreenCredential object representing the given alphabetic password.
129      * If the supplied password is empty, create an empty credential object.
130      */
createPasswordOrNone(@ullable CharSequence password)131     public static LockscreenCredential createPasswordOrNone(@Nullable CharSequence password) {
132         if (TextUtils.isEmpty(password)) {
133             return createNone();
134         } else {
135             return createPassword(password);
136         }
137     }
138 
139     /**
140      * Creates a LockscreenCredential object representing the given numeric PIN.
141      * If the supplied password is empty, create an empty credential object.
142      */
createPinOrNone(@ullable CharSequence pin)143     public static LockscreenCredential createPinOrNone(@Nullable CharSequence pin) {
144         if (TextUtils.isEmpty(pin)) {
145             return createNone();
146         } else {
147             return createPin(pin);
148         }
149     }
150 
ensureNotZeroized()151     private void ensureNotZeroized() {
152         Preconditions.checkState(mCredential != null, "Credential is already zeroized");
153     }
154     /**
155      * Returns the type of this credential. Can be one of {@link #CREDENTIAL_TYPE_NONE},
156      * {@link #CREDENTIAL_TYPE_PATTERN}, {@link #CREDENTIAL_TYPE_PIN} or
157      * {@link #CREDENTIAL_TYPE_PASSWORD}.
158      */
getType()159     public int getType() {
160         ensureNotZeroized();
161         return mType;
162     }
163 
164     /**
165      * Returns the credential bytes. This is a direct reference of the internal field so
166      * callers should not modify it.
167      *
168      */
getCredential()169     public byte[] getCredential() {
170         ensureNotZeroized();
171         return mCredential;
172     }
173 
174     /**
175      *  Returns the credential type recognized by {@link StorageManager}. Can be one of
176      *  {@link StorageManager#CRYPT_TYPE_DEFAULT}, {@link StorageManager#CRYPT_TYPE_PATTERN},
177      *  {@link StorageManager#CRYPT_TYPE_PIN} or {@link StorageManager#CRYPT_TYPE_PASSWORD}.
178      */
getStorageCryptType()179     public int getStorageCryptType() {
180         if (isNone()) {
181             return StorageManager.CRYPT_TYPE_DEFAULT;
182         }
183         if (isPattern()) {
184             return StorageManager.CRYPT_TYPE_PATTERN;
185         }
186         if (isPin()) {
187             return StorageManager.CRYPT_TYPE_PIN;
188         }
189         if (isPassword()) {
190             return StorageManager.CRYPT_TYPE_PASSWORD;
191         }
192         throw new IllegalStateException("Unhandled credential type");
193     }
194 
195     /** Returns whether this is an empty credential */
isNone()196     public boolean isNone() {
197         ensureNotZeroized();
198         return mType == CREDENTIAL_TYPE_NONE;
199     }
200 
201     /** Returns whether this is a pattern credential */
isPattern()202     public boolean isPattern() {
203         ensureNotZeroized();
204         return mType == CREDENTIAL_TYPE_PATTERN;
205     }
206 
207     /** Returns whether this is a numeric pin credential */
isPin()208     public boolean isPin() {
209         ensureNotZeroized();
210         return mType == CREDENTIAL_TYPE_PIN;
211     }
212 
213     /** Returns whether this is an alphabetic password credential */
isPassword()214     public boolean isPassword() {
215         ensureNotZeroized();
216         return mType == CREDENTIAL_TYPE_PASSWORD;
217     }
218 
219     /** Returns the length of the credential */
size()220     public int size() {
221         ensureNotZeroized();
222         return mCredential.length;
223     }
224 
225     /** Create a copy of the credential */
duplicate()226     public LockscreenCredential duplicate() {
227         return new LockscreenCredential(mType,
228                 mCredential != null ? Arrays.copyOf(mCredential, mCredential.length) : null);
229     }
230 
231     /**
232      * Zeroize the credential bytes.
233      */
zeroize()234     public void zeroize() {
235         if (mCredential != null) {
236             Arrays.fill(mCredential, (byte) 0);
237             mCredential = null;
238         }
239     }
240 
241     /**
242      * Check if the credential meets minimal length requirement.
243      *
244      * @throws IllegalArgumentException if the credential is too short.
245      */
checkLength()246     public void checkLength() {
247         if (isNone()) {
248             return;
249         }
250         if (isPattern()) {
251             if (size() < LockPatternUtils.MIN_LOCK_PATTERN_SIZE) {
252                 throw new IllegalArgumentException("pattern must not be null and at least "
253                         + LockPatternUtils.MIN_LOCK_PATTERN_SIZE + " dots long.");
254             }
255             return;
256         }
257         if (isPassword() || isPin()) {
258             if (size() < LockPatternUtils.MIN_LOCK_PASSWORD_SIZE) {
259                 throw new IllegalArgumentException("password must not be null and at least "
260                         + "of length " + LockPatternUtils.MIN_LOCK_PASSWORD_SIZE);
261             }
262             return;
263         }
264     }
265 
266     /**
267      * Check if this credential's type matches one that's retrieved from disk. The nuance here is
268      * that the framework used to not distinguish between PIN and password, so this method will
269      * allow a PIN/Password LockscreenCredential to match against the legacy
270      * {@link #CREDENTIAL_TYPE_PASSWORD_OR_PIN} stored on disk.
271      */
checkAgainstStoredType(int storedCredentialType)272     public boolean checkAgainstStoredType(int storedCredentialType) {
273         if (storedCredentialType == CREDENTIAL_TYPE_PASSWORD_OR_PIN) {
274             return getType() == CREDENTIAL_TYPE_PASSWORD || getType() == CREDENTIAL_TYPE_PIN;
275         }
276         return getType() == storedCredentialType;
277     }
278 
279     @Override
writeToParcel(Parcel dest, int flags)280     public void writeToParcel(Parcel dest, int flags) {
281         dest.writeInt(mType);
282         dest.writeByteArray(mCredential);
283     }
284 
285     public static final Parcelable.Creator<LockscreenCredential> CREATOR =
286             new Parcelable.Creator<LockscreenCredential>() {
287 
288         @Override
289         public LockscreenCredential createFromParcel(Parcel source) {
290             return new LockscreenCredential(source.readInt(), source.createByteArray());
291         }
292 
293         @Override
294         public LockscreenCredential[] newArray(int size) {
295             return new LockscreenCredential[size];
296         }
297     };
298 
299     @Override
describeContents()300     public int describeContents() {
301         return 0;
302     }
303 
304     @Override
close()305     public void close() {
306         zeroize();
307     }
308 
309     @Override
hashCode()310     public int hashCode() {
311         // Effective Java — Always override hashCode when you override equals
312         return (17 + mType) * 31 + mCredential.hashCode();
313     }
314 
315     @Override
equals(Object o)316     public boolean equals(Object o) {
317         if (o == this) return true;
318         if (!(o instanceof LockscreenCredential)) return false;
319         final LockscreenCredential other = (LockscreenCredential) o;
320         return mType == other.mType && Arrays.equals(mCredential, other.mCredential);
321     }
322 
323     /**
324      * Converts a CharSequence to a byte array without requiring a toString(), which creates an
325      * additional copy.
326      *
327      * @param chars The CharSequence to convert
328      * @return A byte array representing the input
329      */
charSequenceToByteArray(CharSequence chars)330     private static byte[] charSequenceToByteArray(CharSequence chars) {
331         if (chars == null) {
332             return new byte[0];
333         }
334         byte[] bytes = new byte[chars.length()];
335         for (int i = 0; i < chars.length(); i++) {
336             bytes[i] = (byte) chars.charAt(i);
337         }
338         return bytes;
339     }
340 }
341