• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 libcore.util.HexEncoding;
35 
36 import java.security.MessageDigest;
37 import java.security.NoSuchAlgorithmException;
38 import java.util.Arrays;
39 import java.util.List;
40 import java.util.Objects;
41 
42 /**
43  * A class representing a lockscreen credential. It can be either an empty password, a pattern
44  * or a password (or PIN).
45  *
46  * <p> As required by some security certification, the framework tries its best to
47  * remove copies of the lockscreen credential bytes from memory. In this regard, this class
48  * abuses the {@link AutoCloseable} interface for sanitizing memory. This
49  * presents a nice syntax to auto-zeroize memory with the try-with-resource statement:
50  * <pre>
51  * try {LockscreenCredential credential = LockscreenCredential.createPassword(...) {
52  *     // Process the credential in some way
53  * }
54  * </pre>
55  * With this construct, we can guarantee that there will be no copies of the password left in
56  * memory when the credential goes out of scope. This should help mitigate certain class of
57  * attacks where the attcker gains read-only access to full device memory (cold boot attack,
58  * unsecured software/hardware memory dumping interfaces such as JTAG).
59  */
60 public class LockscreenCredential implements Parcelable, AutoCloseable {
61 
62     private final int mType;
63     // Stores raw credential bytes, or null if credential has been zeroized. An empty password
64     // is represented as a byte array of length 0.
65     private byte[] mCredential;
66 
67     /**
68      * Private constructor, use static builder methods instead.
69      *
70      * <p> Builder methods should create a private copy of the credential bytes and pass in here.
71      * LockscreenCredential will only store the reference internally without copying. This is to
72      * minimize the number of extra copies introduced.
73      */
LockscreenCredential(int type, byte[] credential)74     private LockscreenCredential(int type, byte[] credential) {
75         Objects.requireNonNull(credential);
76         if (type == CREDENTIAL_TYPE_NONE) {
77             Preconditions.checkArgument(credential.length == 0);
78         } else {
79             // Do not allow constructing a CREDENTIAL_TYPE_PASSWORD_OR_PIN object.
80             Preconditions.checkArgument(type == CREDENTIAL_TYPE_PIN
81                     || type == CREDENTIAL_TYPE_PASSWORD
82                     || type == CREDENTIAL_TYPE_PATTERN);
83             Preconditions.checkArgument(credential.length > 0);
84         }
85         mType = type;
86         mCredential = credential;
87     }
88 
89     /**
90      * Creates a LockscreenCredential object representing empty password.
91      */
createNone()92     public static LockscreenCredential createNone() {
93         return new LockscreenCredential(CREDENTIAL_TYPE_NONE, new byte[0]);
94     }
95 
96     /**
97      * Creates a LockscreenCredential object representing the given pattern.
98      */
createPattern(@onNull List<LockPatternView.Cell> pattern)99     public static LockscreenCredential createPattern(@NonNull List<LockPatternView.Cell> pattern) {
100         return new LockscreenCredential(CREDENTIAL_TYPE_PATTERN,
101                 LockPatternUtils.patternToByteArray(pattern));
102     }
103 
104     /**
105      * Creates a LockscreenCredential object representing the given alphabetic password.
106      */
createPassword(@onNull CharSequence password)107     public static LockscreenCredential createPassword(@NonNull CharSequence password) {
108         return new LockscreenCredential(CREDENTIAL_TYPE_PASSWORD,
109                 charSequenceToByteArray(password));
110     }
111 
112     /**
113      * Creates a LockscreenCredential object representing a managed password for profile with
114      * unified challenge. This credentiall will have type {@code CREDENTIAL_TYPE_PASSWORD} for now.
115      * TODO: consider add a new credential type for this. This can then supersede the
116      * isLockTiedToParent argument in various places in LSS.
117      */
createManagedPassword(@onNull byte[] password)118     public static LockscreenCredential createManagedPassword(@NonNull byte[] password) {
119         return new LockscreenCredential(CREDENTIAL_TYPE_PASSWORD,
120                 Arrays.copyOf(password, password.length));
121     }
122 
123     /**
124      * Creates a LockscreenCredential object representing the given numeric PIN.
125      */
createPin(@onNull CharSequence pin)126     public static LockscreenCredential createPin(@NonNull CharSequence pin) {
127         return new LockscreenCredential(CREDENTIAL_TYPE_PIN,
128                 charSequenceToByteArray(pin));
129     }
130 
131     /**
132      * Creates a LockscreenCredential object representing the given alphabetic password.
133      * If the supplied password is empty, create an empty credential object.
134      */
createPasswordOrNone(@ullable CharSequence password)135     public static LockscreenCredential createPasswordOrNone(@Nullable CharSequence password) {
136         if (TextUtils.isEmpty(password)) {
137             return createNone();
138         } else {
139             return createPassword(password);
140         }
141     }
142 
143     /**
144      * Creates a LockscreenCredential object representing the given numeric PIN.
145      * If the supplied password is empty, create an empty credential object.
146      */
createPinOrNone(@ullable CharSequence pin)147     public static LockscreenCredential createPinOrNone(@Nullable CharSequence pin) {
148         if (TextUtils.isEmpty(pin)) {
149             return createNone();
150         } else {
151             return createPin(pin);
152         }
153     }
154 
ensureNotZeroized()155     private void ensureNotZeroized() {
156         Preconditions.checkState(mCredential != null, "Credential is already zeroized");
157     }
158     /**
159      * Returns the type of this credential. Can be one of {@link #CREDENTIAL_TYPE_NONE},
160      * {@link #CREDENTIAL_TYPE_PATTERN}, {@link #CREDENTIAL_TYPE_PIN} or
161      * {@link #CREDENTIAL_TYPE_PASSWORD}.
162      */
getType()163     public int getType() {
164         ensureNotZeroized();
165         return mType;
166     }
167 
168     /**
169      * Returns the credential bytes. This is a direct reference of the internal field so
170      * callers should not modify it.
171      *
172      */
getCredential()173     public byte[] getCredential() {
174         ensureNotZeroized();
175         return mCredential;
176     }
177 
178     /**
179      *  Returns the credential type recognized by {@link StorageManager}. Can be one of
180      *  {@link StorageManager#CRYPT_TYPE_DEFAULT}, {@link StorageManager#CRYPT_TYPE_PATTERN},
181      *  {@link StorageManager#CRYPT_TYPE_PIN} or {@link StorageManager#CRYPT_TYPE_PASSWORD}.
182      */
getStorageCryptType()183     public int getStorageCryptType() {
184         if (isNone()) {
185             return StorageManager.CRYPT_TYPE_DEFAULT;
186         }
187         if (isPattern()) {
188             return StorageManager.CRYPT_TYPE_PATTERN;
189         }
190         if (isPin()) {
191             return StorageManager.CRYPT_TYPE_PIN;
192         }
193         if (isPassword()) {
194             return StorageManager.CRYPT_TYPE_PASSWORD;
195         }
196         throw new IllegalStateException("Unhandled credential type");
197     }
198 
199     /** Returns whether this is an empty credential */
isNone()200     public boolean isNone() {
201         ensureNotZeroized();
202         return mType == CREDENTIAL_TYPE_NONE;
203     }
204 
205     /** Returns whether this is a pattern credential */
isPattern()206     public boolean isPattern() {
207         ensureNotZeroized();
208         return mType == CREDENTIAL_TYPE_PATTERN;
209     }
210 
211     /** Returns whether this is a numeric pin credential */
isPin()212     public boolean isPin() {
213         ensureNotZeroized();
214         return mType == CREDENTIAL_TYPE_PIN;
215     }
216 
217     /** Returns whether this is an alphabetic password credential */
isPassword()218     public boolean isPassword() {
219         ensureNotZeroized();
220         return mType == CREDENTIAL_TYPE_PASSWORD;
221     }
222 
223     /** Returns the length of the credential */
size()224     public int size() {
225         ensureNotZeroized();
226         return mCredential.length;
227     }
228 
229     /** Create a copy of the credential */
duplicate()230     public LockscreenCredential duplicate() {
231         return new LockscreenCredential(mType,
232                 mCredential != null ? Arrays.copyOf(mCredential, mCredential.length) : null);
233     }
234 
235     /**
236      * Zeroize the credential bytes.
237      */
zeroize()238     public void zeroize() {
239         if (mCredential != null) {
240             Arrays.fill(mCredential, (byte) 0);
241             mCredential = null;
242         }
243     }
244 
245     /**
246      * Check if the credential meets minimal length requirement.
247      *
248      * @throws IllegalArgumentException if the credential is too short.
249      */
checkLength()250     public void checkLength() {
251         if (isNone()) {
252             return;
253         }
254         if (isPattern()) {
255             if (size() < LockPatternUtils.MIN_LOCK_PATTERN_SIZE) {
256                 throw new IllegalArgumentException("pattern must not be null and at least "
257                         + LockPatternUtils.MIN_LOCK_PATTERN_SIZE + " dots long.");
258             }
259             return;
260         }
261         if (isPassword() || isPin()) {
262             if (size() < LockPatternUtils.MIN_LOCK_PASSWORD_SIZE) {
263                 throw new IllegalArgumentException("password must not be null and at least "
264                         + "of length " + LockPatternUtils.MIN_LOCK_PASSWORD_SIZE);
265             }
266             return;
267         }
268     }
269 
270     /**
271      * Check if this credential's type matches one that's retrieved from disk. The nuance here is
272      * that the framework used to not distinguish between PIN and password, so this method will
273      * allow a PIN/Password LockscreenCredential to match against the legacy
274      * {@link #CREDENTIAL_TYPE_PASSWORD_OR_PIN} stored on disk.
275      */
checkAgainstStoredType(int storedCredentialType)276     public boolean checkAgainstStoredType(int storedCredentialType) {
277         if (storedCredentialType == CREDENTIAL_TYPE_PASSWORD_OR_PIN) {
278             return getType() == CREDENTIAL_TYPE_PASSWORD || getType() == CREDENTIAL_TYPE_PIN;
279         }
280         return getType() == storedCredentialType;
281     }
282 
283     /**
284      * Hash the password for password history check purpose.
285      */
passwordToHistoryHash(byte[] salt, byte[] hashFactor)286     public String passwordToHistoryHash(byte[] salt, byte[] hashFactor) {
287         return passwordToHistoryHash(mCredential, salt, hashFactor);
288     }
289 
290     /**
291      * Hash the password for password history check purpose.
292      */
passwordToHistoryHash( byte[] passwordToHash, byte[] salt, byte[] hashFactor)293     public static String passwordToHistoryHash(
294             byte[] passwordToHash, byte[] salt, byte[] hashFactor) {
295         if (passwordToHash == null || passwordToHash.length == 0
296                 || hashFactor == null || salt == null) {
297             return null;
298         }
299         try {
300             MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
301             sha256.update(hashFactor);
302             byte[] saltedPassword = Arrays.copyOf(passwordToHash, passwordToHash.length
303                     + salt.length);
304             System.arraycopy(salt, 0, saltedPassword, passwordToHash.length, salt.length);
305             sha256.update(saltedPassword);
306             Arrays.fill(saltedPassword, (byte) 0);
307             return new String(HexEncoding.encode(sha256.digest()));
308         } catch (NoSuchAlgorithmException e) {
309             throw new AssertionError("Missing digest algorithm: ", e);
310         }
311     }
312 
313     /**
314      * Generate a hash for the given password. To avoid brute force attacks, we use a salted hash.
315      * Not the most secure, but it is at least a second level of protection. First level is that
316      * the file is in a location only readable by the system process.
317      *
318      * @return the hash of the pattern in a byte array.
319      */
legacyPasswordToHash(byte[] salt)320     public String legacyPasswordToHash(byte[] salt) {
321         return legacyPasswordToHash(mCredential, salt);
322     }
323 
324     /**
325      * Generate a hash for the given password. To avoid brute force attacks, we use a salted hash.
326      * Not the most secure, but it is at least a second level of protection. First level is that
327      * the file is in a location only readable by the system process.
328      *
329      * @param password the gesture pattern.
330      *
331      * @return the hash of the pattern in a byte array.
332      */
legacyPasswordToHash(byte[] password, byte[] salt)333     public static String legacyPasswordToHash(byte[] password, byte[] salt) {
334         if (password == null || password.length == 0 || salt == null) {
335             return null;
336         }
337 
338         try {
339             // Previously the password was passed as a String with the following code:
340             // byte[] saltedPassword = (password + salt).getBytes();
341             // The code below creates the identical digest preimage using byte arrays:
342             byte[] saltedPassword = Arrays.copyOf(password, password.length + salt.length);
343             System.arraycopy(salt, 0, saltedPassword, password.length, salt.length);
344             byte[] sha1 = MessageDigest.getInstance("SHA-1").digest(saltedPassword);
345             byte[] md5 = MessageDigest.getInstance("MD5").digest(saltedPassword);
346 
347             byte[] combined = new byte[sha1.length + md5.length];
348             System.arraycopy(sha1, 0, combined, 0, sha1.length);
349             System.arraycopy(md5, 0, combined, sha1.length, md5.length);
350 
351             final char[] hexEncoded = HexEncoding.encode(combined);
352             Arrays.fill(saltedPassword, (byte) 0);
353             return new String(hexEncoded);
354         } catch (NoSuchAlgorithmException e) {
355             throw new AssertionError("Missing digest algorithm: ", e);
356         }
357     }
358 
359     @Override
writeToParcel(Parcel dest, int flags)360     public void writeToParcel(Parcel dest, int flags) {
361         dest.writeInt(mType);
362         dest.writeByteArray(mCredential);
363     }
364 
365     public static final Parcelable.Creator<LockscreenCredential> CREATOR =
366             new Parcelable.Creator<LockscreenCredential>() {
367 
368         @Override
369         public LockscreenCredential createFromParcel(Parcel source) {
370             return new LockscreenCredential(source.readInt(), source.createByteArray());
371         }
372 
373         @Override
374         public LockscreenCredential[] newArray(int size) {
375             return new LockscreenCredential[size];
376         }
377     };
378 
379     @Override
describeContents()380     public int describeContents() {
381         return 0;
382     }
383 
384     @Override
close()385     public void close() {
386         zeroize();
387     }
388 
389     @Override
hashCode()390     public int hashCode() {
391         // Effective Java — Always override hashCode when you override equals
392         return (17 + mType) * 31 + mCredential.hashCode();
393     }
394 
395     @Override
equals(Object o)396     public boolean equals(Object o) {
397         if (o == this) return true;
398         if (!(o instanceof LockscreenCredential)) return false;
399         final LockscreenCredential other = (LockscreenCredential) o;
400         return mType == other.mType && Arrays.equals(mCredential, other.mCredential);
401     }
402 
403     /**
404      * Converts a CharSequence to a byte array without requiring a toString(), which creates an
405      * additional copy.
406      *
407      * @param chars The CharSequence to convert
408      * @return A byte array representing the input
409      */
charSequenceToByteArray(CharSequence chars)410     private static byte[] charSequenceToByteArray(CharSequence chars) {
411         if (chars == null) {
412             return new byte[0];
413         }
414         byte[] bytes = new byte[chars.length()];
415         for (int i = 0; i < chars.length(); i++) {
416             bytes[i] = (byte) chars.charAt(i);
417         }
418         return bytes;
419     }
420 }
421