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