2efd2dbbac
If the user has supplied a backup password in Settings, that password is validated during the full backup process and is used as an encryption key for encoding the backed-up data itself. This is the fundamental mechanism whereby users can secure their data even against malicious parties getting physical unlocked access to their device. Technically the user-supplied password is not used as the encryption key for the backed-up data itself. What is actually done is that a random key is generated to use as the raw encryption key. THAT key, in turn, is encrypted with the user-supplied password (after random salting and key expansion with PBKDF2). The encrypted master key and a checksum are stored in the backup header. At restore time, the user supplies their password, which allows the system to decrypt the master key, which in turn allows the decryption of the backup data itself. The checksum is part of the archive in order to permit validation of the user-supplied password. The checksum is the result of running the user-supplied password through PBKDF2 with a randomly selected salt. At restore time, the proposed password is run through PBKDF2 with the salt described by the archive header. If the result does not match the archive's stated checksum, then the user has supplied the wrong decryption password. Also, suppress backup consideration for a few packages whose data is either nonexistent or inapplicable across devices or factory reset operations. Bug 4901637 Change-Id: Id0cc9d0fdfc046602b129f273d48e23b7a14df36
154 lines
6.5 KiB
Java
154 lines
6.5 KiB
Java
/*
|
|
* Copyright (C) 2009 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;
|
|
|
|
|
|
import android.app.backup.BackupDataInput;
|
|
import android.app.backup.BackupDataOutput;
|
|
import android.app.backup.BackupAgentHelper;
|
|
import android.app.backup.FullBackup;
|
|
import android.app.backup.FullBackupDataOutput;
|
|
import android.app.backup.WallpaperBackupHelper;
|
|
import android.content.Context;
|
|
import android.os.ParcelFileDescriptor;
|
|
import android.os.ServiceManager;
|
|
import android.util.Slog;
|
|
|
|
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
|
|
/**
|
|
* Backup agent for various system-managed data, currently just the system wallpaper
|
|
*/
|
|
public class SystemBackupAgent extends BackupAgentHelper {
|
|
private static final String TAG = "SystemBackupAgent";
|
|
|
|
// These paths must match what the WallpaperManagerService uses. The leaf *_FILENAME
|
|
// are also used in the full-backup file format, so must not change unless steps are
|
|
// taken to support the legacy backed-up datasets.
|
|
private static final String WALLPAPER_IMAGE_FILENAME = "wallpaper";
|
|
private static final String WALLPAPER_INFO_FILENAME = "wallpaper_info.xml";
|
|
|
|
private static final String WALLPAPER_IMAGE_DIR = "/data/data/com.android.settings/files";
|
|
private static final String WALLPAPER_IMAGE = WALLPAPER_IMAGE_DIR + "/" + WALLPAPER_IMAGE_FILENAME;
|
|
|
|
private static final String WALLPAPER_INFO_DIR = "/data/system";
|
|
private static final String WALLPAPER_INFO = WALLPAPER_INFO_DIR + "/" + WALLPAPER_INFO_FILENAME;
|
|
|
|
|
|
@Override
|
|
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
|
|
ParcelFileDescriptor newState) throws IOException {
|
|
// We only back up the data under the current "wallpaper" schema with metadata
|
|
WallpaperManagerService wallpaper = (WallpaperManagerService)ServiceManager.getService(
|
|
Context.WALLPAPER_SERVICE);
|
|
String[] files = new String[] { WALLPAPER_IMAGE, WALLPAPER_INFO };
|
|
if (wallpaper != null && wallpaper.mName != null && wallpaper.mName.length() > 0) {
|
|
// When the wallpaper has a name, back up the info by itself.
|
|
// TODO: Don't rely on the innards of the service object like this!
|
|
// TODO: Send a delete for any stored wallpaper image in this case?
|
|
files = new String[] { WALLPAPER_INFO };
|
|
}
|
|
addHelper("wallpaper", new WallpaperBackupHelper(SystemBackupAgent.this, files));
|
|
super.onBackup(oldState, data, newState);
|
|
}
|
|
|
|
@Override
|
|
public void onFullBackup(FullBackupDataOutput data) throws IOException {
|
|
// At present we back up only the wallpaper
|
|
fullWallpaperBackup(data);
|
|
}
|
|
|
|
private void fullWallpaperBackup(FullBackupDataOutput output) {
|
|
// Back up the data files directly. We do them in this specific order --
|
|
// info file followed by image -- because then we need take no special
|
|
// steps during restore; the restore will happen properly when the individual
|
|
// files are restored piecemeal.
|
|
FullBackup.backupToTar(getPackageName(), FullBackup.ROOT_TREE_TOKEN, null,
|
|
WALLPAPER_INFO_DIR, WALLPAPER_INFO, output.getData());
|
|
FullBackup.backupToTar(getPackageName(), FullBackup.ROOT_TREE_TOKEN, null,
|
|
WALLPAPER_IMAGE_DIR, WALLPAPER_IMAGE, output.getData());
|
|
}
|
|
|
|
@Override
|
|
public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
|
|
throws IOException {
|
|
// On restore, we also support a previous data schema "system_files"
|
|
addHelper("wallpaper", new WallpaperBackupHelper(SystemBackupAgent.this,
|
|
new String[] { WALLPAPER_IMAGE, WALLPAPER_INFO }));
|
|
addHelper("system_files", new WallpaperBackupHelper(SystemBackupAgent.this,
|
|
new String[] { WALLPAPER_IMAGE }));
|
|
|
|
try {
|
|
super.onRestore(data, appVersionCode, newState);
|
|
|
|
WallpaperManagerService wallpaper = (WallpaperManagerService)ServiceManager.getService(
|
|
Context.WALLPAPER_SERVICE);
|
|
wallpaper.settingsRestored();
|
|
} catch (IOException ex) {
|
|
// If there was a failure, delete everything for the wallpaper, this is too aggressive,
|
|
// but this is hopefully a rare failure.
|
|
Slog.d(TAG, "restore failed", ex);
|
|
(new File(WALLPAPER_IMAGE)).delete();
|
|
(new File(WALLPAPER_INFO)).delete();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onRestoreFile(ParcelFileDescriptor data, long size,
|
|
int type, String domain, String path, long mode, long mtime)
|
|
throws IOException {
|
|
Slog.i(TAG, "Restoring file domain=" + domain + " path=" + path);
|
|
|
|
// Bits to indicate postprocessing we may need to perform
|
|
boolean restoredWallpaper = false;
|
|
|
|
File outFile = null;
|
|
// Various domain+files we understand a priori
|
|
if (domain.equals(FullBackup.ROOT_TREE_TOKEN)) {
|
|
if (path.equals(WALLPAPER_INFO_FILENAME)) {
|
|
outFile = new File(WALLPAPER_INFO);
|
|
restoredWallpaper = true;
|
|
} else if (path.equals(WALLPAPER_IMAGE_FILENAME)) {
|
|
outFile = new File(WALLPAPER_IMAGE);
|
|
restoredWallpaper = true;
|
|
}
|
|
}
|
|
|
|
try {
|
|
if (outFile == null) {
|
|
Slog.w(TAG, "Skipping unrecognized system file: [ " + domain + " : " + path + " ]");
|
|
}
|
|
FullBackup.restoreFile(data, size, type, mode, mtime, outFile);
|
|
|
|
if (restoredWallpaper) {
|
|
WallpaperManagerService wallpaper =
|
|
(WallpaperManagerService)ServiceManager.getService(
|
|
Context.WALLPAPER_SERVICE);
|
|
wallpaper.settingsRestored();
|
|
}
|
|
} catch (IOException e) {
|
|
if (restoredWallpaper) {
|
|
// Make sure we wind up in a good state
|
|
(new File(WALLPAPER_IMAGE)).delete();
|
|
(new File(WALLPAPER_INFO)).delete();
|
|
}
|
|
}
|
|
}
|
|
}
|