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