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.content.pm.UserInfo; 21 import android.os.UserHandle; 22 import android.os.UserManager; 23 import android.security.keystore.KeyGenParameterSpec; 24 import android.security.keystore.KeyProperties; 25 import android.security.keystore.UserNotAuthenticatedException; 26 import android.util.Slog; 27 import android.util.SparseArray; 28 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 * Caches *unified* work challenge for user 0's managed profiles. Only user 0's profile is supported 47 * at the moment because the cached credential is encrypted using a keystore key auth-bound to 48 * user 0: this is to match how unified work challenge is similarly auth-bound to its parent user's 49 * lockscreen credential normally. It's possible to extend this class to support managed profiles 50 * for secondary users, that will require generating auth-bound keys to their corresponding parent 51 * user though (which {@link KeyGenParameterSpec} does not support right now). 52 * 53 * <p> The cache is filled whenever the managed profile's unified challenge is created or derived 54 * (as part of the parent user's credential verification flow). It's removed when the profile is 55 * deleted or a (separate) lockscreen credential is explicitly set on the profile. There is also 56 * an ADB command to evict the cache "cmd lock_settings remove-cache --user X", to assist 57 * development and testing. 58 59 * <p> The encrypted credential is stored in-memory only so the cache does not persist across 60 * reboots. 61 */ 62 public class ManagedProfilePasswordCache { 63 64 private static final String TAG = "ManagedProfilePasswordCache"; 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 private final UserManager mUserManager; 71 ManagedProfilePasswordCache(KeyStore keyStore, UserManager userManager)72 public ManagedProfilePasswordCache(KeyStore keyStore, UserManager userManager) { 73 mKeyStore = keyStore; 74 mUserManager = userManager; 75 } 76 77 /** 78 * Encrypt and store the password in the cache. Does NOT overwrite existing password cache 79 * if one for the given user already exists. 80 */ storePassword(int userId, LockscreenCredential password)81 public void storePassword(int userId, LockscreenCredential password) { 82 synchronized (mEncryptedPasswords) { 83 if (mEncryptedPasswords.contains(userId)) { 84 return; 85 } 86 UserInfo parent = mUserManager.getProfileParent(userId); 87 if (parent == null || parent.id != UserHandle.USER_SYSTEM) { 88 // Since the cached password is encrypted using a keystore key auth-bound to user 0, 89 // only support caching password for user 0's profile. 90 return; 91 } 92 String keyName = getEncryptionKeyName(userId); 93 KeyGenerator generator; 94 SecretKey key; 95 try { 96 generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, 97 mKeyStore.getProvider()); 98 generator.init(new KeyGenParameterSpec.Builder( 99 keyName, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) 100 .setKeySize(KEY_LENGTH) 101 .setBlockModes(KeyProperties.BLOCK_MODE_GCM) 102 .setNamespace(SyntheticPasswordCrypto.keyNamespace()) 103 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) 104 // Generate auth-bound key to user 0 (since we the caller is user 0) 105 .setUserAuthenticationRequired(true) 106 .setUserAuthenticationValidityDurationSeconds(CACHE_TIMEOUT_SECONDS) 107 .build()); 108 key = generator.generateKey(); 109 } catch (GeneralSecurityException e) { 110 Slog.e(TAG, "Cannot generate key", e); 111 return; 112 } 113 114 Cipher cipher; 115 try { 116 cipher = Cipher.getInstance("AES/GCM/NoPadding"); 117 cipher.init(Cipher.ENCRYPT_MODE, key); 118 byte[] ciphertext = cipher.doFinal(password.getCredential()); 119 byte[] iv = cipher.getIV(); 120 byte[] block = Arrays.copyOf(iv, ciphertext.length + iv.length); 121 System.arraycopy(ciphertext, 0, block, iv.length, ciphertext.length); 122 mEncryptedPasswords.put(userId, block); 123 } catch (GeneralSecurityException e) { 124 Slog.d(TAG, "Cannot encrypt", e); 125 } 126 } 127 } 128 129 /** Attempt to retrieve the password for the given user. Returns {@code null} if it's not in the 130 * cache or if decryption fails. 131 */ retrievePassword(int userId)132 public @Nullable LockscreenCredential retrievePassword(int userId) { 133 synchronized (mEncryptedPasswords) { 134 byte[] block = mEncryptedPasswords.get(userId); 135 if (block == null) { 136 return null; 137 } 138 Key key; 139 try { 140 key = mKeyStore.getKey(getEncryptionKeyName(userId), null); 141 } catch (UnrecoverableKeyException | KeyStoreException | NoSuchAlgorithmException e) { 142 Slog.d(TAG, "Cannot get key", e); 143 return null; 144 } 145 if (key == null) { 146 return null; 147 } 148 byte[] iv = Arrays.copyOf(block, 12); 149 byte[] ciphertext = Arrays.copyOfRange(block, 12, block.length); 150 byte[] credential; 151 try { 152 Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); 153 cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv)); 154 credential = cipher.doFinal(ciphertext); 155 } catch (UserNotAuthenticatedException e) { 156 Slog.i(TAG, "Device not unlocked for more than 7 days"); 157 return null; 158 } catch (GeneralSecurityException e) { 159 Slog.d(TAG, "Cannot decrypt", e); 160 return null; 161 } 162 LockscreenCredential result = LockscreenCredential.createManagedPassword(credential); 163 Arrays.fill(credential, (byte) 0); 164 return result; 165 } 166 } 167 168 /** Remove the given user's password from cache, if one exists. */ removePassword(int userId)169 public void removePassword(int userId) { 170 synchronized (mEncryptedPasswords) { 171 String keyName = getEncryptionKeyName(userId); 172 String legacyKeyName = getLegacyEncryptionKeyName(userId); 173 try { 174 if (mKeyStore.containsAlias(keyName)) { 175 mKeyStore.deleteEntry(keyName); 176 } 177 if (mKeyStore.containsAlias(legacyKeyName)) { 178 mKeyStore.deleteEntry(legacyKeyName); 179 } 180 } catch (KeyStoreException e) { 181 Slog.d(TAG, "Cannot delete key", e); 182 } 183 if (mEncryptedPasswords.contains(userId)) { 184 Arrays.fill(mEncryptedPasswords.get(userId), (byte) 0); 185 mEncryptedPasswords.remove(userId); 186 } 187 } 188 } 189 getEncryptionKeyName(int userId)190 private static String getEncryptionKeyName(int userId) { 191 return "com.android.server.locksettings.unified_profile_cache_v2_" + userId; 192 } 193 194 /** 195 * Returns the legacy keystore key name when setUnlockedDeviceRequired() was set explicitly. 196 * Only existed during Android 11 internal testing period. 197 */ getLegacyEncryptionKeyName(int userId)198 private static String getLegacyEncryptionKeyName(int userId) { 199 return "com.android.server.locksettings.unified_profile_cache_" + userId; 200 } 201 } 202