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