1 /* 2 * Copyright (C) 2020 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.server.locksettings; 18 19 import android.annotation.Nullable; 20 import android.security.GateKeeper; 21 import android.security.keystore.KeyGenParameterSpec; 22 import android.security.keystore.KeyProperties; 23 import android.security.keystore.UserNotAuthenticatedException; 24 import android.util.Slog; 25 import android.util.SparseArray; 26 27 import com.android.internal.annotations.VisibleForTesting; 28 import com.android.internal.util.ArrayUtils; 29 import com.android.internal.widget.LockPatternUtils; 30 import com.android.internal.widget.LockscreenCredential; 31 32 import java.security.GeneralSecurityException; 33 import java.security.Key; 34 import java.security.KeyStore; 35 import java.security.KeyStoreException; 36 import java.security.NoSuchAlgorithmException; 37 import java.security.UnrecoverableKeyException; 38 import java.util.Arrays; 39 import java.util.concurrent.TimeUnit; 40 41 import javax.crypto.Cipher; 42 import javax.crypto.KeyGenerator; 43 import javax.crypto.SecretKey; 44 import javax.crypto.spec.GCMParameterSpec; 45 46 /** 47 * An in-memory cache for unified profile passwords. A "unified profile password" is the random 48 * password that the system automatically generates and manages for each profile that uses a unified 49 * challenge and where the parent user has a secure lock screen. 50 * <p> 51 * Each password in this cache is encrypted by a Keystore key that is auth-bound to the parent user. 52 * This is very similar to how the password is protected on-disk, but the in-memory cache uses a 53 * much longer timeout on the keys: 7 days instead of 30 seconds. This enables use cases like 54 * unpausing work apps without requiring authentication as frequently. 55 * <p> 56 * Unified profile passwords are cached when they are created, or when they are decrypted as part of 57 * the parent user's LSKF verification flow. They are removed when the profile is deleted or when a 58 * separate challenge is explicitly set on the profile. There is also an ADB command to evict a 59 * cached password, "locksettings remove-cache --user X", to assist development and testing. 60 */ 61 @VisibleForTesting // public visibility is needed for Mockito 62 public class UnifiedProfilePasswordCache { 63 64 private static final String TAG = "UnifiedProfilePasswordCache"; 65 private static final int KEY_LENGTH = 256; 66 private static final int CACHE_TIMEOUT_SECONDS = (int) TimeUnit.DAYS.toSeconds(7); 67 68 private final SparseArray<byte[]> mEncryptedPasswords = new SparseArray<>(); 69 private final KeyStore mKeyStore; 70 UnifiedProfilePasswordCache(KeyStore keyStore)71 public UnifiedProfilePasswordCache(KeyStore keyStore) { 72 mKeyStore = keyStore; 73 } 74 75 /** 76 * Encrypt and store the password in the cache. Does NOT overwrite existing password cache 77 * if one for the given user already exists. 78 * 79 * Should only be called on a profile userId. 80 */ storePassword(int userId, LockscreenCredential password, long parentSid)81 public void storePassword(int userId, LockscreenCredential password, long parentSid) { 82 if (parentSid == GateKeeper.INVALID_SECURE_USER_ID) return; 83 synchronized (mEncryptedPasswords) { 84 if (mEncryptedPasswords.contains(userId)) { 85 return; 86 } 87 String keyName = getEncryptionKeyName(userId); 88 KeyGenerator generator; 89 SecretKey key; 90 try { 91 generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, 92 mKeyStore.getProvider()); 93 generator.init(new KeyGenParameterSpec.Builder( 94 keyName, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) 95 .setKeySize(KEY_LENGTH) 96 .setBlockModes(KeyProperties.BLOCK_MODE_GCM) 97 .setNamespace(SyntheticPasswordCrypto.keyNamespace()) 98 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) 99 .setUserAuthenticationRequired(true) 100 .setBoundToSpecificSecureUserId(parentSid) 101 .setUserAuthenticationValidityDurationSeconds(CACHE_TIMEOUT_SECONDS) 102 .build()); 103 key = generator.generateKey(); 104 } catch (GeneralSecurityException e) { 105 Slog.e(TAG, "Cannot generate key", e); 106 return; 107 } 108 109 Cipher cipher; 110 try { 111 cipher = Cipher.getInstance("AES/GCM/NoPadding"); 112 cipher.init(Cipher.ENCRYPT_MODE, key); 113 byte[] ciphertext = cipher.doFinal(password.getCredential()); 114 byte[] iv = cipher.getIV(); 115 byte[] block = ArrayUtils.concat(iv, ciphertext); 116 mEncryptedPasswords.put(userId, block); 117 } catch (GeneralSecurityException e) { 118 Slog.d(TAG, "Cannot encrypt", e); 119 } 120 } 121 } 122 123 /** Attempt to retrieve the password for the given user. Returns {@code null} if it's not in the 124 * cache or if decryption fails. 125 */ retrievePassword(int userId)126 public @Nullable LockscreenCredential retrievePassword(int userId) { 127 synchronized (mEncryptedPasswords) { 128 byte[] block = mEncryptedPasswords.get(userId); 129 if (block == null) { 130 return null; 131 } 132 Key key; 133 try { 134 key = mKeyStore.getKey(getEncryptionKeyName(userId), null); 135 } catch (UnrecoverableKeyException | KeyStoreException | NoSuchAlgorithmException e) { 136 Slog.d(TAG, "Cannot get key", e); 137 return null; 138 } 139 if (key == null) { 140 return null; 141 } 142 byte[] iv = Arrays.copyOf(block, 12); 143 byte[] ciphertext = Arrays.copyOfRange(block, 12, block.length); 144 byte[] credential; 145 try { 146 Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); 147 cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv)); 148 credential = cipher.doFinal(ciphertext); 149 } catch (UserNotAuthenticatedException e) { 150 Slog.i(TAG, "Device not unlocked for more than 7 days"); 151 return null; 152 } catch (GeneralSecurityException e) { 153 Slog.d(TAG, "Cannot decrypt", e); 154 return null; 155 } 156 LockscreenCredential result = 157 LockscreenCredential.createUnifiedProfilePassword(credential); 158 LockPatternUtils.zeroize(credential); 159 return result; 160 } 161 } 162 163 /** Remove the given user's password from cache, if one exists. */ removePassword(int userId)164 public void removePassword(int userId) { 165 synchronized (mEncryptedPasswords) { 166 String keyName = getEncryptionKeyName(userId); 167 String legacyKeyName = getLegacyEncryptionKeyName(userId); 168 try { 169 if (mKeyStore.containsAlias(keyName)) { 170 mKeyStore.deleteEntry(keyName); 171 } 172 if (mKeyStore.containsAlias(legacyKeyName)) { 173 mKeyStore.deleteEntry(legacyKeyName); 174 } 175 } catch (KeyStoreException e) { 176 Slog.d(TAG, "Cannot delete key", e); 177 } 178 if (mEncryptedPasswords.contains(userId)) { 179 LockPatternUtils.zeroize(mEncryptedPasswords.get(userId)); 180 mEncryptedPasswords.remove(userId); 181 } 182 } 183 } 184 getEncryptionKeyName(int userId)185 private static String getEncryptionKeyName(int userId) { 186 return "com.android.server.locksettings.unified_profile_cache_v2_" + userId; 187 } 188 189 /** 190 * Returns the legacy keystore key name when setUnlockedDeviceRequired() was set explicitly. 191 * Only existed during Android 11 internal testing period. 192 */ getLegacyEncryptionKeyName(int userId)193 private static String getLegacyEncryptionKeyName(int userId) { 194 return "com.android.server.locksettings.unified_profile_cache_" + userId; 195 } 196 } 197