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.server.locksettings.recoverablekeystore.storage; 18 19 import android.content.Context; 20 import android.os.ServiceSpecificException; 21 import android.os.UserHandle; 22 import android.os.UserManager; 23 import android.util.Log; 24 25 import com.android.internal.annotations.VisibleForTesting; 26 import com.android.server.locksettings.recoverablekeystore.WrappedKey; 27 28 import java.util.ArrayList; 29 import java.util.List; 30 import java.util.Map; 31 32 /** 33 * Cleans up data when user is removed. 34 */ 35 public class CleanupManager { 36 private static final String TAG = "CleanupManager"; 37 38 private final UserManager mUserManager; 39 private final RecoverableKeyStoreDb mDatabase; 40 private final RecoverySnapshotStorage mSnapshotStorage; 41 private final ApplicationKeyStorage mApplicationKeyStorage; 42 43 // Serial number can not be changed at runtime. 44 private Map<Integer, Long> mSerialNumbers; // Always in sync with the database. 45 46 /** 47 * Creates a new instance of the class. 48 * IMPORTANT: {@code verifyKnownUsers} must be called before the first data access. 49 */ getInstance( Context context, RecoverySnapshotStorage snapshotStorage, RecoverableKeyStoreDb recoverableKeyStoreDb, ApplicationKeyStorage applicationKeyStorage)50 public static CleanupManager getInstance( 51 Context context, 52 RecoverySnapshotStorage snapshotStorage, 53 RecoverableKeyStoreDb recoverableKeyStoreDb, 54 ApplicationKeyStorage applicationKeyStorage) { 55 return new CleanupManager( 56 snapshotStorage, 57 recoverableKeyStoreDb, 58 UserManager.get(context), 59 applicationKeyStorage); 60 } 61 62 @VisibleForTesting CleanupManager( RecoverySnapshotStorage snapshotStorage, RecoverableKeyStoreDb recoverableKeyStoreDb, UserManager userManager, ApplicationKeyStorage applicationKeyStorage)63 CleanupManager( 64 RecoverySnapshotStorage snapshotStorage, 65 RecoverableKeyStoreDb recoverableKeyStoreDb, 66 UserManager userManager, 67 ApplicationKeyStorage applicationKeyStorage) { 68 mSnapshotStorage = snapshotStorage; 69 mDatabase = recoverableKeyStoreDb; 70 mUserManager = userManager; 71 mApplicationKeyStorage = applicationKeyStorage; 72 } 73 74 /** 75 * Registers recovery agent in the system, if necessary. 76 */ registerRecoveryAgent(int userId, int uid)77 public synchronized void registerRecoveryAgent(int userId, int uid) { 78 if (mSerialNumbers == null) { 79 // Table was uninitialized. 80 verifyKnownUsers(); 81 } 82 // uid is ignored since recovery agent is a system app. 83 Long storedSerialNumber = mSerialNumbers.get(userId); 84 if (storedSerialNumber == null) { 85 storedSerialNumber = -1L; 86 } 87 if (storedSerialNumber != -1) { 88 // User was already registered. 89 return; 90 } 91 // User was added after {@code verifyAllUsers} call. 92 long currentSerialNumber = mUserManager.getSerialNumberForUser(UserHandle.of(userId)); 93 if (currentSerialNumber != -1) { 94 storeUserSerialNumber(userId, currentSerialNumber); 95 } 96 } 97 98 /** 99 * Removes data if serial number for a user was changed. 100 */ verifyKnownUsers()101 public synchronized void verifyKnownUsers() { 102 mSerialNumbers = mDatabase.getUserSerialNumbers(); 103 List<Integer> deletedUserIds = new ArrayList<Integer>(){}; 104 for (Map.Entry<Integer, Long> entry : mSerialNumbers.entrySet()) { 105 Integer userId = entry.getKey(); 106 Long storedSerialNumber = entry.getValue(); 107 if (storedSerialNumber == null) { 108 storedSerialNumber = -1L; 109 } 110 long currentSerialNumber = mUserManager.getSerialNumberForUser(UserHandle.of(userId)); 111 if (currentSerialNumber == -1) { 112 // User was removed. 113 deletedUserIds.add(userId); 114 removeDataForUser(userId); 115 } else if (storedSerialNumber == -1) { 116 // User is detected for the first time 117 storeUserSerialNumber(userId, currentSerialNumber); 118 } else if (storedSerialNumber != currentSerialNumber) { 119 // User has unexpected serial number - delete data related to old serial number. 120 deletedUserIds.add(userId); 121 removeDataForUser(userId); 122 // Register new user. 123 storeUserSerialNumber(userId, currentSerialNumber); 124 } 125 } 126 127 for (Integer deletedUser : deletedUserIds) { 128 mSerialNumbers.remove(deletedUser); 129 } 130 } 131 storeUserSerialNumber(int userId, long userSerialNumber)132 private void storeUserSerialNumber(int userId, long userSerialNumber) { 133 Log.d(TAG, "Storing serial number for user " + userId + "."); 134 mSerialNumbers.put(userId, userSerialNumber); 135 mDatabase.setUserSerialNumber(userId, userSerialNumber); 136 } 137 138 /** 139 * Removes all data for given user, including 140 * 141 * <ul> 142 * <li> Recovery snapshots for all agents belonging to the {@code userId}. 143 * <li> Entries with data related to {@code userId} from the database. 144 * </ul> 145 */ removeDataForUser(int userId)146 private void removeDataForUser(int userId) { 147 Log.d(TAG, "Removing data for user " + userId + "."); 148 List<Integer> recoveryAgents = mDatabase.getRecoveryAgents(userId); 149 for (Integer uid : recoveryAgents) { 150 mSnapshotStorage.remove(uid); 151 removeAllKeysForRecoveryAgent(userId, uid); 152 } 153 154 mDatabase.removeUserFromAllTables(userId); 155 } 156 157 /** 158 * Removes keys from Android KeyStore for the recovery agent; 159 * Doesn't remove encrypted key material from the database. 160 */ removeAllKeysForRecoveryAgent(int userId, int uid)161 private void removeAllKeysForRecoveryAgent(int userId, int uid) { 162 int generationId = mDatabase.getPlatformKeyGenerationId(userId); 163 Map<String, WrappedKey> allKeys = mDatabase.getAllKeys(userId, uid, generationId); 164 for (String alias : allKeys.keySet()) { 165 try { 166 // Delete KeyStore copy. 167 mApplicationKeyStorage.deleteEntry(userId, uid, alias); 168 } catch (ServiceSpecificException e) { 169 // Ignore errors during key removal. 170 Log.e(TAG, "Error while removing recoverable key " + alias + " : " + e); 171 } 172 } 173 } 174 } 175