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