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