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.content.pm.Signature; 27 import android.database.Cursor; 28 import android.database.sqlite.SQLiteDatabase; 29 import android.os.UserHandle; 30 import android.text.TextUtils; 31 import android.util.Log; 32 import android.util.PackageUtils; 33 import android.util.Pair; 34 import android.util.Slog; 35 import android.util.Xml; 36 import com.android.internal.annotations.GuardedBy; 37 import com.android.internal.content.PackageMonitor; 38 import com.android.internal.util.FastXmlSerializer; 39 import com.android.internal.util.XmlUtils; 40 import com.android.server.accounts.AccountsDb.DeDatabaseHelper; 41 42 import org.xmlpull.v1.XmlPullParser; 43 import org.xmlpull.v1.XmlPullParserException; 44 import org.xmlpull.v1.XmlSerializer; 45 46 import java.io.ByteArrayInputStream; 47 import java.io.ByteArrayOutputStream; 48 import java.io.IOException; 49 import java.nio.charset.StandardCharsets; 50 import java.util.ArrayList; 51 import java.util.List; 52 53 /** 54 * Helper class for backup and restore of account access grants. 55 */ 56 public 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 67 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); 105 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) { 116 break; 117 } 118 } 119 } 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 } 131 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]))) { 143 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); 157 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); 170 171 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; 176 177 final String[] packageNames = packageManager.getPackagesForUid(uid); 178 if (packageNames == null) { 179 continue; 180 } 181 182 for (String packageName : packageNames) { 183 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); 194 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 } 202 } 203 } 204 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; 211 } 212 } 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 } 320