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