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