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