1 /*
2  * Copyright (C) 2016 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.accounts;
18 
19 import android.accounts.Account;
20 import android.accounts.AccountManager;
21 import android.accounts.AccountManagerInternal;
22 import android.annotation.IntRange;
23 import android.annotation.NonNull;
24 import android.content.pm.PackageInfo;
25 import android.content.pm.PackageManager;
26 import android.os.UserHandle;
27 import android.text.TextUtils;
28 import android.util.Log;
29 import android.util.PackageUtils;
30 import android.util.Pair;
31 import android.util.Slog;
32 import android.util.Xml;
33 
34 import com.android.internal.annotations.GuardedBy;
35 import com.android.internal.content.PackageMonitor;
36 import com.android.internal.util.XmlUtils;
37 import com.android.modules.utils.TypedXmlPullParser;
38 import com.android.modules.utils.TypedXmlSerializer;
39 
40 import org.xmlpull.v1.XmlPullParserException;
41 
42 import java.io.ByteArrayInputStream;
43 import java.io.ByteArrayOutputStream;
44 import java.io.IOException;
45 import java.nio.charset.StandardCharsets;
46 import java.util.ArrayList;
47 import java.util.List;
48 
49 /**
50  * Helper class for backup and restore of account access grants.
51  */
52 public final class AccountManagerBackupHelper {
53     private static final String TAG = "AccountManagerBackupHelper";
54 
55     private static final long PENDING_RESTORE_TIMEOUT_MILLIS = 60 * 60 * 1000; // 1 hour
56 
57     private static final String TAG_PERMISSIONS = "permissions";
58     private static final String TAG_PERMISSION = "permission";
59     private static final String ATTR_ACCOUNT_SHA_256 = "account-sha-256";
60     private static final String ATTR_PACKAGE = "package";
61     private static final String ATTR_DIGEST = "digest";
62 
63     private final Object mLock = new Object();
64 
65     private final AccountManagerService mAccountManagerService;
66     private final AccountManagerInternal mAccountManagerInternal;
67 
68     @GuardedBy("mLock")
69     private List<PendingAppPermission> mRestorePendingAppPermissions;
70 
71     @GuardedBy("mLock")
72     private RestorePackageMonitor mRestorePackageMonitor;
73 
74     @GuardedBy("mLock")
75     private Runnable mRestoreCancelCommand;
76 
AccountManagerBackupHelper(AccountManagerService accountManagerService, AccountManagerInternal accountManagerInternal)77     public AccountManagerBackupHelper(AccountManagerService accountManagerService,
78             AccountManagerInternal accountManagerInternal) {
79         mAccountManagerService = accountManagerService;
80         mAccountManagerInternal = accountManagerInternal;
81     }
82 
83     private final class PendingAppPermission {
84         private final @NonNull String accountDigest;
85         private final @NonNull String packageName;
86         private final @NonNull String certDigest;
87         private final @IntRange(from = 0) int userId;
88 
PendingAppPermission(String accountDigest, String packageName, String certDigest, int userId)89         public PendingAppPermission(String accountDigest, String packageName,
90                 String certDigest, int userId) {
91             this.accountDigest = accountDigest;
92             this.packageName = packageName;
93             this.certDigest = certDigest;
94             this.userId = userId;
95         }
96 
apply(PackageManager packageManager)97         public boolean apply(PackageManager packageManager) {
98             Account account = null;
99             AccountManagerService.UserAccounts accounts = mAccountManagerService
100                     .getUserAccounts(userId);
101             synchronized (accounts.dbLock) {
102                 synchronized (accounts.cacheLock) {
103                     for (Account[] accountsPerType : accounts.accountCache.values()) {
104                         for (Account accountPerType : accountsPerType) {
105                             if (accountDigest.equals(PackageUtils.computeSha256Digest(
106                                     accountPerType.name.getBytes()))) {
107                                 account = accountPerType;
108                                 break;
109                             }
110                         }
111                         if (account != null) {
112                             break;
113                         }
114                     }
115                 }
116             }
117             if (account == null) {
118                 return false;
119             }
120             final PackageInfo packageInfo;
121             try {
122                 packageInfo = packageManager.getPackageInfoAsUser(packageName,
123                         PackageManager.GET_SIGNATURES, userId);
124             } catch (PackageManager.NameNotFoundException e) {
125                 return false;
126             }
127 
128             // Before we used only the first signature to compute the SHA 256 but some
129             // apps could be singed by multiple certs and the cert order is undefined.
130             // We prefer the modern computation procedure where all certs are taken
131             // into account but also allow the value from the old computation to allow
132             // restoring backed up grants on an older platform version.
133             final String[] signaturesSha256Digests = PackageUtils.computeSignaturesSha256Digests(
134                     packageInfo.signatures);
135             final String signaturesSha256Digest = PackageUtils.computeSignaturesSha256Digest(
136                     signaturesSha256Digests);
137             if (!certDigest.equals(signaturesSha256Digest) && (packageInfo.signatures.length <= 1
138                     || !certDigest.equals(signaturesSha256Digests[0]))) {
139                 return false;
140             }
141             final int uid = packageInfo.applicationInfo.uid;
142             if (!mAccountManagerInternal.hasAccountAccess(account, uid)) {
143                 mAccountManagerService.grantAppPermission(account,
144                         AccountManager.ACCOUNT_ACCESS_TOKEN_TYPE, uid);
145             }
146             return true;
147         }
148     }
149 
backupAccountAccessPermissions(int userId)150     public byte[] backupAccountAccessPermissions(int userId) {
151         final AccountManagerService.UserAccounts accounts = mAccountManagerService
152                 .getUserAccounts(userId);
153         synchronized (accounts.dbLock) {
154             synchronized (accounts.cacheLock) {
155                 List<Pair<String, Integer>> allAccountGrants = accounts.accountsDb
156                         .findAllAccountGrants();
157                 if (allAccountGrants.isEmpty()) {
158                     return null;
159                 }
160                 try {
161                     ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
162                     final TypedXmlSerializer serializer = Xml.newFastSerializer();
163                     serializer.setOutput(dataStream, StandardCharsets.UTF_8.name());
164                     serializer.startDocument(null, true);
165                     serializer.startTag(null, TAG_PERMISSIONS);
166 
167                     PackageManager packageManager = mAccountManagerService.mContext
168                             .getPackageManager();
169                     for (Pair<String, Integer> grant : allAccountGrants) {
170                         final String accountName = grant.first;
171                         final int uid = grant.second;
172 
173                         final String[] packageNames = packageManager.getPackagesForUid(uid);
174                         if (packageNames == null) {
175                             continue;
176                         }
177 
178                         for (String packageName : packageNames) {
179                             final PackageInfo packageInfo;
180                             try {
181                                 packageInfo = packageManager.getPackageInfoAsUser(packageName,
182                                         PackageManager.GET_SIGNATURES, userId);
183                             } catch (PackageManager.NameNotFoundException e) {
184                                 Slog.i(TAG, "Skipping backup of account access grant for"
185                                         + " non-existing package: " + packageName);
186                                 continue;
187                             }
188                             final String digest = PackageUtils.computeSignaturesSha256Digest(
189                                     packageInfo.signatures);
190                             if (digest != null) {
191                                 serializer.startTag(null, TAG_PERMISSION);
192                                 serializer.attribute(null, ATTR_ACCOUNT_SHA_256,
193                                         PackageUtils.computeSha256Digest(accountName.getBytes()));
194                                 serializer.attribute(null, ATTR_PACKAGE, packageName);
195                                 serializer.attribute(null, ATTR_DIGEST, digest);
196                                 serializer.endTag(null, TAG_PERMISSION);
197                             }
198                         }
199                     }
200                     serializer.endTag(null, TAG_PERMISSIONS);
201                     serializer.endDocument();
202                     serializer.flush();
203                     return dataStream.toByteArray();
204                 } catch (IOException e) {
205                     Log.e(TAG, "Error backing up account access grants", e);
206                     return null;
207                 }
208             }
209         }
210     }
211 
restoreAccountAccessPermissions(byte[] data, int userId)212     public void restoreAccountAccessPermissions(byte[] data, int userId) {
213         try {
214             ByteArrayInputStream dataStream = new ByteArrayInputStream(data);
215             TypedXmlPullParser parser = Xml.newFastPullParser();
216             parser.setInput(dataStream, StandardCharsets.UTF_8.name());
217             PackageManager packageManager = mAccountManagerService.mContext.getPackageManager();
218 
219             final int permissionsOuterDepth = parser.getDepth();
220             while (XmlUtils.nextElementWithin(parser, permissionsOuterDepth)) {
221                 if (!TAG_PERMISSIONS.equals(parser.getName())) {
222                     continue;
223                 }
224                 final int permissionOuterDepth = parser.getDepth();
225                 while (XmlUtils.nextElementWithin(parser, permissionOuterDepth)) {
226                     if (!TAG_PERMISSION.equals(parser.getName())) {
227                         continue;
228                     }
229                     String accountDigest = parser.getAttributeValue(null, ATTR_ACCOUNT_SHA_256);
230                     if (TextUtils.isEmpty(accountDigest)) {
231                         XmlUtils.skipCurrentTag(parser);
232                     }
233                     String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
234                     if (TextUtils.isEmpty(packageName)) {
235                         XmlUtils.skipCurrentTag(parser);
236                     }
237                     String digest =  parser.getAttributeValue(null, ATTR_DIGEST);
238                     if (TextUtils.isEmpty(digest)) {
239                         XmlUtils.skipCurrentTag(parser);
240                     }
241 
242                     PendingAppPermission pendingAppPermission = new PendingAppPermission(
243                             accountDigest, packageName, digest, userId);
244 
245                     if (!pendingAppPermission.apply(packageManager)) {
246                         synchronized (mLock) {
247                             // Start watching before add pending to avoid a missed signal
248                             if (mRestorePackageMonitor == null) {
249                                 mRestorePackageMonitor = new RestorePackageMonitor();
250                                 mRestorePackageMonitor.register(mAccountManagerService.mContext,
251                                         mAccountManagerService.mHandler.getLooper(), true);
252                             }
253                             if (mRestorePendingAppPermissions == null) {
254                                 mRestorePendingAppPermissions = new ArrayList<>();
255                             }
256                             mRestorePendingAppPermissions.add(pendingAppPermission);
257                         }
258                     }
259                 }
260             }
261 
262             // Make sure we eventually prune the in-memory pending restores
263             mRestoreCancelCommand = new CancelRestoreCommand();
264             mAccountManagerService.mHandler.postDelayed(mRestoreCancelCommand,
265                     PENDING_RESTORE_TIMEOUT_MILLIS);
266         } catch (XmlPullParserException | IOException e) {
267             Log.e(TAG, "Error restoring app permissions", e);
268         }
269     }
270 
271     private final class RestorePackageMonitor extends PackageMonitor {
272         @Override
onPackageAdded(String packageName, int uid)273         public void onPackageAdded(String packageName, int uid) {
274             synchronized (mLock) {
275                 if (mRestorePendingAppPermissions == null) {
276                     return;
277                 }
278                 if (UserHandle.getUserId(uid) != UserHandle.USER_SYSTEM) {
279                     return;
280                 }
281                 final int count = mRestorePendingAppPermissions.size();
282                 for (int i = count - 1; i >= 0; i--) {
283                     PendingAppPermission pendingAppPermission =
284                             mRestorePendingAppPermissions.get(i);
285                     if (!pendingAppPermission.packageName.equals(packageName)) {
286                         continue;
287                     }
288                     if (pendingAppPermission.apply(
289                             mAccountManagerService.mContext.getPackageManager())) {
290                         mRestorePendingAppPermissions.remove(i);
291                     }
292                 }
293                 if (mRestorePendingAppPermissions.isEmpty()
294                         && mRestoreCancelCommand != null) {
295                     mAccountManagerService.mHandler.removeCallbacks(mRestoreCancelCommand);
296                     mRestoreCancelCommand.run();
297                     mRestoreCancelCommand = null;
298                 }
299             }
300         }
301     }
302 
303     private final class CancelRestoreCommand implements Runnable {
304         @Override
run()305         public void run() {
306             synchronized (mLock) {
307                 mRestorePendingAppPermissions = null;
308                 if (mRestorePackageMonitor != null) {
309                     mRestorePackageMonitor.unregister();
310                     mRestorePackageMonitor = null;
311                 }
312             }
313         }
314     }
315 }
316