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