blob: 1c77a7f451b42ee87388d56b61b304c875471497 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.accounts;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerInternal;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.util.PackageUtils;
import android.util.Pair;
import android.util.Slog;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;
import com.android.server.accounts.AccountsDb.DeDatabaseHelper;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
/**
* Helper class for backup and restore of account access grants.
*/
public final class AccountManagerBackupHelper {
private static final String TAG = "AccountManagerBackupHelper";
private static final long PENDING_RESTORE_TIMEOUT_MILLIS = 60 * 60 * 1000; // 1 hour
private static final String TAG_PERMISSIONS = "permissions";
private static final String TAG_PERMISSION = "permission";
private static final String ATTR_ACCOUNT_SHA_256 = "account-sha-256";
private static final String ATTR_PACKAGE = "package";
private static final String ATTR_DIGEST = "digest";
private final Object mLock = new Object();
private final AccountManagerService mAccountManagerService;
private final AccountManagerInternal mAccountManagerInternal;
@GuardedBy("mLock")
private List<PendingAppPermission> mRestorePendingAppPermissions;
@GuardedBy("mLock")
private RestorePackageMonitor mRestorePackageMonitor;
@GuardedBy("mLock")
private Runnable mRestoreCancelCommand;
public AccountManagerBackupHelper(AccountManagerService accountManagerService,
AccountManagerInternal accountManagerInternal) {
mAccountManagerService = accountManagerService;
mAccountManagerInternal = accountManagerInternal;
}
private final class PendingAppPermission {
private final @NonNull String accountDigest;
private final @NonNull String packageName;
private final @NonNull String certDigest;
private final @IntRange(from = 0) int userId;
public PendingAppPermission(String accountDigest, String packageName,
String certDigest, int userId) {
this.accountDigest = accountDigest;
this.packageName = packageName;
this.certDigest = certDigest;
this.userId = userId;
}
public boolean apply(PackageManager packageManager) {
Account account = null;
AccountManagerService.UserAccounts accounts = mAccountManagerService
.getUserAccounts(userId);
synchronized (accounts.dbLock) {
synchronized (accounts.cacheLock) {
for (Account[] accountsPerType : accounts.accountCache.values()) {
for (Account accountPerType : accountsPerType) {
if (accountDigest.equals(PackageUtils.computeSha256Digest(
accountPerType.name.getBytes()))) {
account = accountPerType;
break;
}
}
if (account != null) {
break;
}
}
}
}
if (account == null) {
return false;
}
final PackageInfo packageInfo;
try {
packageInfo = packageManager.getPackageInfoAsUser(packageName,
PackageManager.GET_SIGNATURES, userId);
} catch (PackageManager.NameNotFoundException e) {
return false;
}
// Before we used only the first signature to compute the SHA 256 but some
// apps could be singed by multiple certs and the cert order is undefined.
// We prefer the modern computation procedure where all certs are taken
// into account but also allow the value from the old computation to allow
// restoring backed up grants on an older platform version.
final String[] signaturesSha256Digests = PackageUtils.computeSignaturesSha256Digests(
packageInfo.signatures);
final String signaturesSha256Digest = PackageUtils.computeSignaturesSha256Digest(
signaturesSha256Digests);
if (!certDigest.equals(signaturesSha256Digest) && (packageInfo.signatures.length <= 1
|| !certDigest.equals(signaturesSha256Digests[0]))) {
return false;
}
final int uid = packageInfo.applicationInfo.uid;
if (!mAccountManagerInternal.hasAccountAccess(account, uid)) {
mAccountManagerService.grantAppPermission(account,
AccountManager.ACCOUNT_ACCESS_TOKEN_TYPE, uid);
}
return true;
}
}
public byte[] backupAccountAccessPermissions(int userId) {
final AccountManagerService.UserAccounts accounts = mAccountManagerService
.getUserAccounts(userId);
synchronized (accounts.dbLock) {
synchronized (accounts.cacheLock) {
List<Pair<String, Integer>> allAccountGrants = accounts.accountsDb
.findAllAccountGrants();
if (allAccountGrants.isEmpty()) {
return null;
}
try {
ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
final XmlSerializer serializer = new FastXmlSerializer();
serializer.setOutput(dataStream, StandardCharsets.UTF_8.name());
serializer.startDocument(null, true);
serializer.startTag(null, TAG_PERMISSIONS);
PackageManager packageManager = mAccountManagerService.mContext
.getPackageManager();
for (Pair<String, Integer> grant : allAccountGrants) {
final String accountName = grant.first;
final int uid = grant.second;
final String[] packageNames = packageManager.getPackagesForUid(uid);
if (packageNames == null) {
continue;
}
for (String packageName : packageNames) {
final PackageInfo packageInfo;
try {
packageInfo = packageManager.getPackageInfoAsUser(packageName,
PackageManager.GET_SIGNATURES, userId);
} catch (PackageManager.NameNotFoundException e) {
Slog.i(TAG, "Skipping backup of account access grant for"
+ " non-existing package: " + packageName);
continue;
}
final String digest = PackageUtils.computeSignaturesSha256Digest(
packageInfo.signatures);
if (digest != null) {
serializer.startTag(null, TAG_PERMISSION);
serializer.attribute(null, ATTR_ACCOUNT_SHA_256,
PackageUtils.computeSha256Digest(accountName.getBytes()));
serializer.attribute(null, ATTR_PACKAGE, packageName);
serializer.attribute(null, ATTR_DIGEST, digest);
serializer.endTag(null, TAG_PERMISSION);
}
}
}
serializer.endTag(null, TAG_PERMISSIONS);
serializer.endDocument();
serializer.flush();
return dataStream.toByteArray();
} catch (IOException e) {
Log.e(TAG, "Error backing up account access grants", e);
return null;
}
}
}
}
public void restoreAccountAccessPermissions(byte[] data, int userId) {
try {
ByteArrayInputStream dataStream = new ByteArrayInputStream(data);
XmlPullParser parser = Xml.newPullParser();
parser.setInput(dataStream, StandardCharsets.UTF_8.name());
PackageManager packageManager = mAccountManagerService.mContext.getPackageManager();
final int permissionsOuterDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, permissionsOuterDepth)) {
if (!TAG_PERMISSIONS.equals(parser.getName())) {
continue;
}
final int permissionOuterDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, permissionOuterDepth)) {
if (!TAG_PERMISSION.equals(parser.getName())) {
continue;
}
String accountDigest = parser.getAttributeValue(null, ATTR_ACCOUNT_SHA_256);
if (TextUtils.isEmpty(accountDigest)) {
XmlUtils.skipCurrentTag(parser);
}
String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
if (TextUtils.isEmpty(packageName)) {
XmlUtils.skipCurrentTag(parser);
}
String digest = parser.getAttributeValue(null, ATTR_DIGEST);
if (TextUtils.isEmpty(digest)) {
XmlUtils.skipCurrentTag(parser);
}
PendingAppPermission pendingAppPermission = new PendingAppPermission(
accountDigest, packageName, digest, userId);
if (!pendingAppPermission.apply(packageManager)) {
synchronized (mLock) {
// Start watching before add pending to avoid a missed signal
if (mRestorePackageMonitor == null) {
mRestorePackageMonitor = new RestorePackageMonitor();
mRestorePackageMonitor.register(mAccountManagerService.mContext,
mAccountManagerService.mHandler.getLooper(), true);
}
if (mRestorePendingAppPermissions == null) {
mRestorePendingAppPermissions = new ArrayList<>();
}
mRestorePendingAppPermissions.add(pendingAppPermission);
}
}
}
}
// Make sure we eventually prune the in-memory pending restores
mRestoreCancelCommand = new CancelRestoreCommand();
mAccountManagerService.mHandler.postDelayed(mRestoreCancelCommand,
PENDING_RESTORE_TIMEOUT_MILLIS);
} catch (XmlPullParserException | IOException e) {
Log.e(TAG, "Error restoring app permissions", e);
}
}
private final class RestorePackageMonitor extends PackageMonitor {
@Override
public void onPackageAdded(String packageName, int uid) {
synchronized (mLock) {
if (mRestorePendingAppPermissions == null) {
return;
}
if (UserHandle.getUserId(uid) != UserHandle.USER_SYSTEM) {
return;
}
final int count = mRestorePendingAppPermissions.size();
for (int i = count - 1; i >= 0; i--) {
PendingAppPermission pendingAppPermission =
mRestorePendingAppPermissions.get(i);
if (!pendingAppPermission.packageName.equals(packageName)) {
continue;
}
if (pendingAppPermission.apply(
mAccountManagerService.mContext.getPackageManager())) {
mRestorePendingAppPermissions.remove(i);
}
}
if (mRestorePendingAppPermissions.isEmpty()
&& mRestoreCancelCommand != null) {
mAccountManagerService.mHandler.removeCallbacks(mRestoreCancelCommand);
mRestoreCancelCommand.run();
mRestoreCancelCommand = null;
}
}
}
}
private final class CancelRestoreCommand implements Runnable {
@Override
public void run() {
synchronized (mLock) {
mRestorePendingAppPermissions = null;
if (mRestorePackageMonitor != null) {
mRestorePackageMonitor.unregister();
mRestorePackageMonitor = null;
}
}
}
}
}