Home | History | Annotate | Download | only in fullbackup
      1 /*
      2  * Copyright (C) 2017 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.backup.fullbackup;
     18 
     19 import static com.android.server.backup.BackupPasswordManager.PBKDF_CURRENT;
     20 import static com.android.server.backup.BackupManagerService.BACKUP_FILE_HEADER_MAGIC;
     21 import static com.android.server.backup.BackupManagerService.BACKUP_FILE_VERSION;
     22 import static com.android.server.backup.BackupManagerService.DEBUG;
     23 import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
     24 import static com.android.server.backup.BackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
     25 import static com.android.server.backup.BackupManagerService.TAG;
     26 
     27 import android.app.backup.IFullBackupRestoreObserver;
     28 import android.content.pm.ApplicationInfo;
     29 import android.content.pm.PackageInfo;
     30 import android.content.pm.PackageManager;
     31 import android.content.pm.PackageManager.NameNotFoundException;
     32 import android.os.ParcelFileDescriptor;
     33 import android.os.RemoteException;
     34 import android.os.UserHandle;
     35 import android.util.Slog;
     36 
     37 import com.android.server.AppWidgetBackupBridge;
     38 import com.android.server.backup.BackupRestoreTask;
     39 import com.android.server.backup.KeyValueAdbBackupEngine;
     40 import com.android.server.backup.BackupManagerService;
     41 import com.android.server.backup.utils.AppBackupUtils;
     42 import com.android.server.backup.utils.PasswordUtils;
     43 
     44 import java.io.ByteArrayOutputStream;
     45 import java.io.DataOutputStream;
     46 import java.io.FileOutputStream;
     47 import java.io.IOException;
     48 import java.io.OutputStream;
     49 import java.util.ArrayList;
     50 import java.util.Arrays;
     51 import java.util.Iterator;
     52 import java.util.List;
     53 import java.util.Map.Entry;
     54 import java.util.TreeMap;
     55 import java.util.concurrent.atomic.AtomicBoolean;
     56 import java.util.zip.Deflater;
     57 import java.util.zip.DeflaterOutputStream;
     58 
     59 import javax.crypto.Cipher;
     60 import javax.crypto.CipherOutputStream;
     61 import javax.crypto.SecretKey;
     62 import javax.crypto.spec.SecretKeySpec;
     63 
     64 /**
     65  * Full backup task variant used for adb backup.
     66  */
     67 public class PerformAdbBackupTask extends FullBackupTask implements BackupRestoreTask {
     68 
     69     private BackupManagerService backupManagerService;
     70     FullBackupEngine mBackupEngine;
     71     final AtomicBoolean mLatch;
     72 
     73     ParcelFileDescriptor mOutputFile;
     74     DeflaterOutputStream mDeflater;
     75     boolean mIncludeApks;
     76     boolean mIncludeObbs;
     77     boolean mIncludeShared;
     78     boolean mDoWidgets;
     79     boolean mAllApps;
     80     boolean mIncludeSystem;
     81     boolean mCompress;
     82     boolean mKeyValue;
     83     ArrayList<String> mPackages;
     84     PackageInfo mCurrentTarget;
     85     String mCurrentPassword;
     86     String mEncryptPassword;
     87     private final int mCurrentOpToken;
     88 
     89     public PerformAdbBackupTask(BackupManagerService backupManagerService,
     90             ParcelFileDescriptor fd, IFullBackupRestoreObserver observer,
     91             boolean includeApks, boolean includeObbs, boolean includeShared, boolean doWidgets,
     92             String curPassword, String encryptPassword, boolean doAllApps, boolean doSystem,
     93             boolean doCompress, boolean doKeyValue, String[] packages, AtomicBoolean latch) {
     94         super(observer);
     95         this.backupManagerService = backupManagerService;
     96         mCurrentOpToken = backupManagerService.generateRandomIntegerToken();
     97         mLatch = latch;
     98 
     99         mOutputFile = fd;
    100         mIncludeApks = includeApks;
    101         mIncludeObbs = includeObbs;
    102         mIncludeShared = includeShared;
    103         mDoWidgets = doWidgets;
    104         mAllApps = doAllApps;
    105         mIncludeSystem = doSystem;
    106         mPackages = (packages == null)
    107                 ? new ArrayList<String>()
    108                 : new ArrayList<>(Arrays.asList(packages));
    109         mCurrentPassword = curPassword;
    110         // when backing up, if there is a current backup password, we require that
    111         // the user use a nonempty encryption password as well.  if one is supplied
    112         // in the UI we use that, but if the UI was left empty we fall back to the
    113         // current backup password (which was supplied by the user as well).
    114         if (encryptPassword == null || "".equals(encryptPassword)) {
    115             mEncryptPassword = curPassword;
    116         } else {
    117             mEncryptPassword = encryptPassword;
    118         }
    119         if (MORE_DEBUG) {
    120             Slog.w(TAG, "Encrypting backup with passphrase=" + mEncryptPassword);
    121         }
    122         mCompress = doCompress;
    123         mKeyValue = doKeyValue;
    124     }
    125 
    126     void addPackagesToSet(TreeMap<String, PackageInfo> set, List<String> pkgNames) {
    127         for (String pkgName : pkgNames) {
    128             if (!set.containsKey(pkgName)) {
    129                 try {
    130                     PackageInfo info = backupManagerService.getPackageManager().getPackageInfo(
    131                             pkgName,
    132                             PackageManager.GET_SIGNING_CERTIFICATES);
    133                     set.put(pkgName, info);
    134                 } catch (NameNotFoundException e) {
    135                     Slog.w(TAG, "Unknown package " + pkgName + ", skipping");
    136                 }
    137             }
    138         }
    139     }
    140 
    141     private OutputStream emitAesBackupHeader(StringBuilder headerbuf,
    142             OutputStream ofstream) throws Exception {
    143         // User key will be used to encrypt the master key.
    144         byte[] newUserSalt = backupManagerService
    145                 .randomBytes(PasswordUtils.PBKDF2_SALT_SIZE);
    146         SecretKey userKey = PasswordUtils
    147                 .buildPasswordKey(PBKDF_CURRENT, mEncryptPassword,
    148                         newUserSalt,
    149                         PasswordUtils.PBKDF2_HASH_ROUNDS);
    150 
    151         // the master key is random for each backup
    152         byte[] masterPw = new byte[256 / 8];
    153         backupManagerService.getRng().nextBytes(masterPw);
    154         byte[] checksumSalt = backupManagerService
    155                 .randomBytes(PasswordUtils.PBKDF2_SALT_SIZE);
    156 
    157         // primary encryption of the datastream with the random key
    158         Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
    159         SecretKeySpec masterKeySpec = new SecretKeySpec(masterPw, "AES");
    160         c.init(Cipher.ENCRYPT_MODE, masterKeySpec);
    161         OutputStream finalOutput = new CipherOutputStream(ofstream, c);
    162 
    163         // line 4: name of encryption algorithm
    164         headerbuf.append(PasswordUtils.ENCRYPTION_ALGORITHM_NAME);
    165         headerbuf.append('\n');
    166         // line 5: user password salt [hex]
    167         headerbuf.append(PasswordUtils.byteArrayToHex(newUserSalt));
    168         headerbuf.append('\n');
    169         // line 6: master key checksum salt [hex]
    170         headerbuf.append(PasswordUtils.byteArrayToHex(checksumSalt));
    171         headerbuf.append('\n');
    172         // line 7: number of PBKDF2 rounds used [decimal]
    173         headerbuf.append(PasswordUtils.PBKDF2_HASH_ROUNDS);
    174         headerbuf.append('\n');
    175 
    176         // line 8: IV of the user key [hex]
    177         Cipher mkC = Cipher.getInstance("AES/CBC/PKCS5Padding");
    178         mkC.init(Cipher.ENCRYPT_MODE, userKey);
    179 
    180         byte[] IV = mkC.getIV();
    181         headerbuf.append(PasswordUtils.byteArrayToHex(IV));
    182         headerbuf.append('\n');
    183 
    184         // line 9: master IV + key blob, encrypted by the user key [hex].  Blob format:
    185         //    [byte] IV length = Niv
    186         //    [array of Niv bytes] IV itself
    187         //    [byte] master key length = Nmk
    188         //    [array of Nmk bytes] master key itself
    189         //    [byte] MK checksum hash length = Nck
    190         //    [array of Nck bytes] master key checksum hash
    191         //
    192         // The checksum is the (master key + checksum salt), run through the
    193         // stated number of PBKDF2 rounds
    194         IV = c.getIV();
    195         byte[] mk = masterKeySpec.getEncoded();
    196         byte[] checksum = PasswordUtils
    197                 .makeKeyChecksum(PBKDF_CURRENT,
    198                         masterKeySpec.getEncoded(),
    199                         checksumSalt, PasswordUtils.PBKDF2_HASH_ROUNDS);
    200 
    201         ByteArrayOutputStream blob = new ByteArrayOutputStream(IV.length + mk.length
    202                 + checksum.length + 3);
    203         DataOutputStream mkOut = new DataOutputStream(blob);
    204         mkOut.writeByte(IV.length);
    205         mkOut.write(IV);
    206         mkOut.writeByte(mk.length);
    207         mkOut.write(mk);
    208         mkOut.writeByte(checksum.length);
    209         mkOut.write(checksum);
    210         mkOut.flush();
    211         byte[] encryptedMk = mkC.doFinal(blob.toByteArray());
    212         headerbuf.append(PasswordUtils.byteArrayToHex(encryptedMk));
    213         headerbuf.append('\n');
    214 
    215         return finalOutput;
    216     }
    217 
    218     private void finalizeBackup(OutputStream out) {
    219         try {
    220             // A standard 'tar' EOF sequence: two 512-byte blocks of all zeroes.
    221             byte[] eof = new byte[512 * 2]; // newly allocated == zero filled
    222             out.write(eof);
    223         } catch (IOException e) {
    224             Slog.w(TAG, "Error attempting to finalize backup stream");
    225         }
    226     }
    227 
    228     @Override
    229     public void run() {
    230         String includeKeyValue = mKeyValue ? ", including key-value backups" : "";
    231         Slog.i(TAG, "--- Performing adb backup" + includeKeyValue + " ---");
    232 
    233         TreeMap<String, PackageInfo> packagesToBackup = new TreeMap<>();
    234         FullBackupObbConnection obbConnection = new FullBackupObbConnection(
    235                 backupManagerService);
    236         obbConnection.establish();  // we'll want this later
    237 
    238         sendStartBackup();
    239         PackageManager pm = backupManagerService.getPackageManager();
    240 
    241         // doAllApps supersedes the package set if any
    242         if (mAllApps) {
    243             List<PackageInfo> allPackages = pm.getInstalledPackages(
    244                     PackageManager.GET_SIGNING_CERTIFICATES);
    245             for (int i = 0; i < allPackages.size(); i++) {
    246                 PackageInfo pkg = allPackages.get(i);
    247                 // Exclude system apps if we've been asked to do so
    248                 if (mIncludeSystem == true
    249                         || ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0)) {
    250                     packagesToBackup.put(pkg.packageName, pkg);
    251                 }
    252             }
    253         }
    254 
    255         // If we're doing widget state as well, ensure that we have all the involved
    256         // host & provider packages in the set
    257         if (mDoWidgets) {
    258             // TODO: http://b/22388012
    259             List<String> pkgs =
    260                     AppWidgetBackupBridge.getWidgetParticipants(UserHandle.USER_SYSTEM);
    261             if (pkgs != null) {
    262                 if (MORE_DEBUG) {
    263                     Slog.i(TAG, "Adding widget participants to backup set:");
    264                     StringBuilder sb = new StringBuilder(128);
    265                     sb.append("   ");
    266                     for (String s : pkgs) {
    267                         sb.append(' ');
    268                         sb.append(s);
    269                     }
    270                     Slog.i(TAG, sb.toString());
    271                 }
    272                 addPackagesToSet(packagesToBackup, pkgs);
    273             }
    274         }
    275 
    276         // Now process the command line argument packages, if any. Note that explicitly-
    277         // named system-partition packages will be included even if includeSystem was
    278         // set to false.
    279         if (mPackages != null) {
    280             addPackagesToSet(packagesToBackup, mPackages);
    281         }
    282 
    283         // Now we cull any inapplicable / inappropriate packages from the set.  This
    284         // includes the special shared-storage agent package; we handle that one
    285         // explicitly at the end of the backup pass. Packages supporting key-value backup are
    286         // added to their own queue, and handled after packages supporting fullbackup.
    287         ArrayList<PackageInfo> keyValueBackupQueue = new ArrayList<>();
    288         Iterator<Entry<String, PackageInfo>> iter = packagesToBackup.entrySet().iterator();
    289         while (iter.hasNext()) {
    290             PackageInfo pkg = iter.next().getValue();
    291             if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo, pm)
    292                     || AppBackupUtils.appIsStopped(pkg.applicationInfo)) {
    293                 iter.remove();
    294                 if (DEBUG) {
    295                     Slog.i(TAG, "Package " + pkg.packageName
    296                             + " is not eligible for backup, removing.");
    297                 }
    298             } else if (AppBackupUtils.appIsKeyValueOnly(pkg)) {
    299                 iter.remove();
    300                 if (DEBUG) {
    301                     Slog.i(TAG, "Package " + pkg.packageName
    302                             + " is key-value.");
    303                 }
    304                 keyValueBackupQueue.add(pkg);
    305             }
    306         }
    307 
    308         // flatten the set of packages now so we can explicitly control the ordering
    309         ArrayList<PackageInfo> backupQueue =
    310                 new ArrayList<>(packagesToBackup.values());
    311         FileOutputStream ofstream = new FileOutputStream(mOutputFile.getFileDescriptor());
    312         OutputStream out = null;
    313 
    314         PackageInfo pkg = null;
    315         try {
    316             boolean encrypting = (mEncryptPassword != null && mEncryptPassword.length() > 0);
    317 
    318             // Only allow encrypted backups of encrypted devices
    319             if (backupManagerService.deviceIsEncrypted() && !encrypting) {
    320                 Slog.e(TAG, "Unencrypted backup of encrypted device; aborting");
    321                 return;
    322             }
    323 
    324             OutputStream finalOutput = ofstream;
    325 
    326             // Verify that the given password matches the currently-active
    327             // backup password, if any
    328             if (!backupManagerService.backupPasswordMatches(mCurrentPassword)) {
    329                 if (DEBUG) {
    330                     Slog.w(TAG, "Backup password mismatch; aborting");
    331                 }
    332                 return;
    333             }
    334 
    335             // Write the global file header.  All strings are UTF-8 encoded; lines end
    336             // with a '\n' byte.  Actual backup data begins immediately following the
    337             // final '\n'.
    338             //
    339             // line 1: "ANDROID BACKUP"
    340             // line 2: backup file format version, currently "5"
    341             // line 3: compressed?  "0" if not compressed, "1" if compressed.
    342             // line 4: name of encryption algorithm [currently only "none" or "AES-256"]
    343             //
    344             // When line 4 is not "none", then additional header data follows:
    345             //
    346             // line 5: user password salt [hex]
    347             // line 6: master key checksum salt [hex]
    348             // line 7: number of PBKDF2 rounds to use (same for user & master) [decimal]
    349             // line 8: IV of the user key [hex]
    350             // line 9: master key blob [hex]
    351             //     IV of the master key, master key itself, master key checksum hash
    352             //
    353             // The master key checksum is the master key plus its checksum salt, run through
    354             // 10k rounds of PBKDF2.  This is used to verify that the user has supplied the
    355             // correct password for decrypting the archive:  the master key decrypted from
    356             // the archive using the user-supplied password is also run through PBKDF2 in
    357             // this way, and if the result does not match the checksum as stored in the
    358             // archive, then we know that the user-supplied password does not match the
    359             // archive's.
    360             StringBuilder headerbuf = new StringBuilder(1024);
    361 
    362             headerbuf.append(BACKUP_FILE_HEADER_MAGIC);
    363             headerbuf.append(BACKUP_FILE_VERSION); // integer, no trailing \n
    364             headerbuf.append(mCompress ? "\n1\n" : "\n0\n");
    365 
    366             try {
    367                 // Set up the encryption stage if appropriate, and emit the correct header
    368                 if (encrypting) {
    369                     finalOutput = emitAesBackupHeader(headerbuf, finalOutput);
    370                 } else {
    371                     headerbuf.append("none\n");
    372                 }
    373 
    374                 byte[] header = headerbuf.toString().getBytes("UTF-8");
    375                 ofstream.write(header);
    376 
    377                 // Set up the compression stage feeding into the encryption stage (if any)
    378                 if (mCompress) {
    379                     Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION);
    380                     finalOutput = new DeflaterOutputStream(finalOutput, deflater, true);
    381                 }
    382 
    383                 out = finalOutput;
    384             } catch (Exception e) {
    385                 // Should never happen!
    386                 Slog.e(TAG, "Unable to emit archive header", e);
    387                 return;
    388             }
    389 
    390             // Shared storage if requested
    391             if (mIncludeShared) {
    392                 try {
    393                     pkg = backupManagerService.getPackageManager().getPackageInfo(
    394                             SHARED_BACKUP_AGENT_PACKAGE, 0);
    395                     backupQueue.add(pkg);
    396                 } catch (NameNotFoundException e) {
    397                     Slog.e(TAG, "Unable to find shared-storage backup handler");
    398                 }
    399             }
    400 
    401             // Now actually run the constructed backup sequence for full backup
    402             int N = backupQueue.size();
    403             for (int i = 0; i < N; i++) {
    404                 pkg = backupQueue.get(i);
    405                 if (DEBUG) {
    406                     Slog.i(TAG, "--- Performing full backup for package " + pkg.packageName
    407                             + " ---");
    408                 }
    409                 final boolean isSharedStorage =
    410                         pkg.packageName.equals(
    411                                 SHARED_BACKUP_AGENT_PACKAGE);
    412 
    413                 mBackupEngine = new FullBackupEngine(backupManagerService, out,
    414                         null, pkg, mIncludeApks, this, Long.MAX_VALUE,
    415                         mCurrentOpToken, /*transportFlags=*/ 0);
    416                 sendOnBackupPackage(isSharedStorage ? "Shared storage" : pkg.packageName);
    417 
    418                 // Don't need to check preflight result as there is no preflight hook.
    419                 mCurrentTarget = pkg;
    420                 mBackupEngine.backupOnePackage();
    421 
    422                 // after the app's agent runs to handle its private filesystem
    423                 // contents, back up any OBB content it has on its behalf.
    424                 if (mIncludeObbs && !isSharedStorage) {
    425                     boolean obbOkay = obbConnection.backupObbs(pkg, out);
    426                     if (!obbOkay) {
    427                         throw new RuntimeException("Failure writing OBB stack for " + pkg);
    428                     }
    429                 }
    430             }
    431             // And for key-value backup if enabled
    432             if (mKeyValue) {
    433                 for (PackageInfo keyValuePackage : keyValueBackupQueue) {
    434                     if (DEBUG) {
    435                         Slog.i(TAG, "--- Performing key-value backup for package "
    436                                 + keyValuePackage.packageName + " ---");
    437                     }
    438                     KeyValueAdbBackupEngine kvBackupEngine =
    439                             new KeyValueAdbBackupEngine(out, keyValuePackage,
    440                                     backupManagerService,
    441                                     backupManagerService.getPackageManager(),
    442                                     backupManagerService.getBaseStateDir(),
    443                                     backupManagerService.getDataDir());
    444                     sendOnBackupPackage(keyValuePackage.packageName);
    445                     kvBackupEngine.backupOnePackage();
    446                 }
    447             }
    448 
    449             // Done!
    450             finalizeBackup(out);
    451         } catch (RemoteException e) {
    452             Slog.e(TAG, "App died during full backup");
    453         } catch (Exception e) {
    454             Slog.e(TAG, "Internal exception during full backup", e);
    455         } finally {
    456             try {
    457                 if (out != null) {
    458                     out.flush();
    459                     out.close();
    460                 }
    461                 mOutputFile.close();
    462             } catch (IOException e) {
    463                 Slog.e(TAG, "IO error closing adb backup file: " + e.getMessage());
    464             }
    465             synchronized (mLatch) {
    466                 mLatch.set(true);
    467                 mLatch.notifyAll();
    468             }
    469             sendEndBackup();
    470             obbConnection.tearDown();
    471             if (DEBUG) {
    472                 Slog.d(TAG, "Full backup pass complete.");
    473             }
    474             backupManagerService.getWakelock().release();
    475         }
    476     }
    477 
    478     // BackupRestoreTask methods, used for timeout handling
    479     @Override
    480     public void execute() {
    481         // Unused
    482     }
    483 
    484     @Override
    485     public void operationComplete(long result) {
    486         // Unused
    487     }
    488 
    489     @Override
    490     public void handleCancel(boolean cancelAll) {
    491         final PackageInfo target = mCurrentTarget;
    492         if (DEBUG) {
    493             Slog.w(TAG, "adb backup cancel of " + target);
    494         }
    495         if (target != null) {
    496             backupManagerService.tearDownAgentAndKill(mCurrentTarget.applicationInfo);
    497         }
    498         backupManagerService.removeOperation(mCurrentOpToken);
    499     }
    500 }
    501