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