• 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     /** Returns whether this is an empty credential */
isNone()179     public boolean isNone() {
180         ensureNotZeroized();
181         return mType == CREDENTIAL_TYPE_NONE;
182     }
183 
184     /** Returns whether this is a pattern credential */
isPattern()185     public boolean isPattern() {
186         ensureNotZeroized();
187         return mType == CREDENTIAL_TYPE_PATTERN;
188     }
189 
190     /** Returns whether this is a numeric pin credential */
isPin()191     public boolean isPin() {
192         ensureNotZeroized();
193         return mType == CREDENTIAL_TYPE_PIN;
194     }
195 
196     /** Returns whether this is an alphabetic password credential */
isPassword()197     public boolean isPassword() {
198         ensureNotZeroized();
199         return mType == CREDENTIAL_TYPE_PASSWORD;
200     }
201 
202     /** Returns the length of the credential */
size()203     public int size() {
204         ensureNotZeroized();
205         return mCredential.length;
206     }
207 
208     /** Create a copy of the credential */
duplicate()209     public LockscreenCredential duplicate() {
210         return new LockscreenCredential(mType,
211                 mCredential != null ? Arrays.copyOf(mCredential, mCredential.length) : null);
212     }
213 
214     /**
215      * Zeroize the credential bytes.
216      */
zeroize()217     public void zeroize() {
218         if (mCredential != null) {
219             Arrays.fill(mCredential, (byte) 0);
220             mCredential = null;
221         }
222     }
223 
224     /**
225      * Check if the credential meets minimal length requirement.
226      *
227      * @throws IllegalArgumentException if the credential is too short.
228      */
checkLength()229     public void checkLength() {
230         if (isNone()) {
231             return;
232         }
233         if (isPattern()) {
234             if (size() < LockPatternUtils.MIN_LOCK_PATTERN_SIZE) {
235                 throw new IllegalArgumentException("pattern must not be null and at least "
236                         + LockPatternUtils.MIN_LOCK_PATTERN_SIZE + " dots long.");
237             }
238             return;
239         }
240         if (isPassword() || isPin()) {
241             if (size() < LockPatternUtils.MIN_LOCK_PASSWORD_SIZE) {
242                 throw new IllegalArgumentException("password must not be null and at least "
243                         + "of length " + LockPatternUtils.MIN_LOCK_PASSWORD_SIZE);
244             }
245             return;
246         }
247     }
248 
249     /**
250      * Check if this credential's type matches one that's retrieved from disk. The nuance here is
251      * that the framework used to not distinguish between PIN and password, so this method will
252      * allow a PIN/Password LockscreenCredential to match against the legacy
253      * {@link #CREDENTIAL_TYPE_PASSWORD_OR_PIN} stored on disk.
254      */
checkAgainstStoredType(int storedCredentialType)255     public boolean checkAgainstStoredType(int storedCredentialType) {
256         if (storedCredentialType == CREDENTIAL_TYPE_PASSWORD_OR_PIN) {
257             return getType() == CREDENTIAL_TYPE_PASSWORD || getType() == CREDENTIAL_TYPE_PIN;
258         }
259         return getType() == storedCredentialType;
260     }
261 
262     /**
263      * Hash the password for password history check purpose.
264      */
passwordToHistoryHash(byte[] salt, byte[] hashFactor)265     public String passwordToHistoryHash(byte[] salt, byte[] hashFactor) {
266         return passwordToHistoryHash(mCredential, salt, hashFactor);
267     }
268 
269     /**
270      * Hash the password for password history check purpose.
271      */
passwordToHistoryHash( byte[] passwordToHash, byte[] salt, byte[] hashFactor)272     public static String passwordToHistoryHash(
273             byte[] passwordToHash, byte[] salt, byte[] hashFactor) {
274         if (passwordToHash == null || passwordToHash.length == 0
275                 || hashFactor == null || salt == null) {
276             return null;
277         }
278         try {
279             MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
280             sha256.update(hashFactor);
281             byte[] saltedPassword = Arrays.copyOf(passwordToHash, passwordToHash.length
282                     + salt.length);
283             System.arraycopy(salt, 0, saltedPassword, passwordToHash.length, salt.length);
284             sha256.update(saltedPassword);
285             Arrays.fill(saltedPassword, (byte) 0);
286             return new String(HexEncoding.encode(sha256.digest()));
287         } catch (NoSuchAlgorithmException e) {
288             throw new AssertionError("Missing digest algorithm: ", e);
289         }
290     }
291 
292     /**
293      * Generate a hash for the given password. To avoid brute force attacks, we use a salted hash.
294      * Not the most secure, but it is at least a second level of protection. First level is that
295      * the file is in a location only readable by the system process.
296      *
297      * @return the hash of the pattern in a byte array.
298      */
legacyPasswordToHash(byte[] salt)299     public String legacyPasswordToHash(byte[] salt) {
300         return legacyPasswordToHash(mCredential, salt);
301     }
302 
303     /**
304      * Generate a hash for the given password. To avoid brute force attacks, we use a salted hash.
305      * Not the most secure, but it is at least a second level of protection. First level is that
306      * the file is in a location only readable by the system process.
307      *
308      * @param password the gesture pattern.
309      *
310      * @return the hash of the pattern in a byte array.
311      */
legacyPasswordToHash(byte[] password, byte[] salt)312     public static String legacyPasswordToHash(byte[] password, byte[] salt) {
313         if (password == null || password.length == 0 || salt == null) {
314             return null;
315         }
316 
317         try {
318             // Previously the password was passed as a String with the following code:
319             // byte[] saltedPassword = (password + salt).getBytes();
320             // The code below creates the identical digest preimage using byte arrays:
321             byte[] saltedPassword = Arrays.copyOf(password, password.length + salt.length);
322             System.arraycopy(salt, 0, saltedPassword, password.length, salt.length);
323             byte[] sha1 = MessageDigest.getInstance("SHA-1").digest(saltedPassword);
324             byte[] md5 = MessageDigest.getInstance("MD5").digest(saltedPassword);
325 
326             byte[] combined = new byte[sha1.length + md5.length];
327             System.arraycopy(sha1, 0, combined, 0, sha1.length);
328             System.arraycopy(md5, 0, combined, sha1.length, md5.length);
329 
330             final char[] hexEncoded = HexEncoding.encode(combined);
331             Arrays.fill(saltedPassword, (byte) 0);
332             return new String(hexEncoded);
333         } catch (NoSuchAlgorithmException e) {
334             throw new AssertionError("Missing digest algorithm: ", e);
335         }
336     }
337 
338     @Override
writeToParcel(Parcel dest, int flags)339     public void writeToParcel(Parcel dest, int flags) {
340         dest.writeInt(mType);
341         dest.writeByteArray(mCredential);
342     }
343 
344     public static final Parcelable.Creator<LockscreenCredential> CREATOR =
345             new Parcelable.Creator<LockscreenCredential>() {
346 
347         @Override
348         public LockscreenCredential createFromParcel(Parcel source) {
349             return new LockscreenCredential(source.readInt(), source.createByteArray());
350         }
351 
352         @Override
353         public LockscreenCredential[] newArray(int size) {
354             return new LockscreenCredential[size];
355         }
356     };
357 
358     @Override
describeContents()359     public int describeContents() {
360         return 0;
361     }
362 
363     @Override
close()364     public void close() {
365         zeroize();
366     }
367 
368     @Override
hashCode()369     public int hashCode() {
370         // Effective Java — Always override hashCode when you override equals
371         return (17 + mType) * 31 + mCredential.hashCode();
372     }
373 
374     @Override
equals(Object o)375     public boolean equals(Object o) {
376         if (o == this) return true;
377         if (!(o instanceof LockscreenCredential)) return false;
378         final LockscreenCredential other = (LockscreenCredential) o;
379         return mType == other.mType && Arrays.equals(mCredential, other.mCredential);
380     }
381 
382     /**
383      * Converts a CharSequence to a byte array without requiring a toString(), which creates an
384      * additional copy.
385      *
386      * @param chars The CharSequence to convert
387      * @return A byte array representing the input
388      */
charSequenceToByteArray(CharSequence chars)389     private static byte[] charSequenceToByteArray(CharSequence chars) {
390         if (chars == null) {
391             return new byte[0];
392         }
393         byte[] bytes = new byte[chars.length()];
394         for (int i = 0; i < chars.length(); i++) {
395             bytes[i] = (byte) chars.charAt(i);
396         }
397         return bytes;
398     }
399 }
400