1 /* 2 * Copyright (C) 2019 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.permissioncontroller.permission.service; 18 19 import static android.content.Context.MODE_PRIVATE; 20 import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED; 21 import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED; 22 import static android.content.pm.PackageManager.GET_PERMISSIONS; 23 import static android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES; 24 import static android.util.Xml.newSerializer; 25 26 import static com.android.permissioncontroller.Constants.DELAYED_RESTORE_PERMISSIONS_FILE; 27 28 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; 29 import static org.xmlpull.v1.XmlPullParser.END_TAG; 30 import static org.xmlpull.v1.XmlPullParser.START_TAG; 31 32 import static java.nio.charset.StandardCharsets.UTF_8; 33 34 import android.content.Context; 35 import android.content.pm.ApplicationInfo; 36 import android.content.pm.PackageInfo; 37 import android.content.pm.PackageManager; 38 import android.content.pm.Signature; 39 import android.content.pm.SigningInfo; 40 import android.os.Build; 41 import android.os.UserHandle; 42 import android.permission.PermissionManager; 43 import android.permission.PermissionManager.SplitPermissionInfo; 44 import android.util.ArraySet; 45 import android.util.Base64; 46 import android.util.Log; 47 import android.util.Xml; 48 49 import androidx.annotation.NonNull; 50 import androidx.annotation.Nullable; 51 import androidx.core.os.BuildCompat; 52 53 import com.android.permissioncontroller.Constants; 54 import com.android.permissioncontroller.permission.model.AppPermissionGroup; 55 import com.android.permissioncontroller.permission.model.AppPermissions; 56 import com.android.permissioncontroller.permission.model.Permission; 57 import com.android.permissioncontroller.permission.utils.CollectionUtils; 58 59 import org.xmlpull.v1.XmlPullParser; 60 import org.xmlpull.v1.XmlPullParserException; 61 import org.xmlpull.v1.XmlSerializer; 62 63 import java.io.FileInputStream; 64 import java.io.IOException; 65 import java.io.OutputStream; 66 import java.security.MessageDigest; 67 import java.security.NoSuchAlgorithmException; 68 import java.util.ArrayList; 69 import java.util.Arrays; 70 import java.util.HashSet; 71 import java.util.List; 72 import java.util.Set; 73 74 /** 75 * Helper for creating and restoring permission backups. 76 */ 77 public class BackupHelper { 78 private static final String LOG_TAG = BackupHelper.class.getSimpleName(); 79 80 private static final String TAG_PERMISSION_BACKUP = "perm-grant-backup"; 81 private static final String ATTR_PLATFORM_VERSION = "version"; 82 83 private static final String TAG_ALL_GRANTS = "rt-grants"; 84 85 private static final String TAG_GRANT = "grant"; 86 private static final String ATTR_PACKAGE_NAME = "pkg"; 87 88 private static final String TAG_SIGNING_INFO = "sign"; 89 private static final String TAG_CURRENT_CERTIFICATE = "curr-cert"; 90 private static final String TAG_PAST_CERTIFICATE = "past-cert"; 91 private static final String ATTR_CERTIFICATE_DIGEST = "digest"; 92 93 private static final String TAG_PERMISSION = "perm"; 94 private static final String ATTR_PERMISSION_NAME = "name"; 95 private static final String ATTR_IS_GRANTED = "g"; 96 private static final String ATTR_USER_SET = "set"; 97 private static final String ATTR_USER_FIXED = "fixed"; 98 private static final String ATTR_WAS_REVIEWED = "was-reviewed"; 99 100 /** Flags of permissions to <u>not</u> back up */ 101 private static final int SYSTEM_RUNTIME_GRANT_MASK = FLAG_PERMISSION_POLICY_FIXED 102 | FLAG_PERMISSION_SYSTEM_FIXED; 103 104 /** Make sure only one user can change the delayed permissions at a time */ 105 private static final Object sLock = new Object(); 106 107 private final Context mContext; 108 109 /** 110 * Create a new backup utils for a user. 111 * 112 * @param context A context to use 113 * @param user The user that is backed up / restored 114 */ BackupHelper(@onNull Context context, @NonNull UserHandle user)115 public BackupHelper(@NonNull Context context, @NonNull UserHandle user) { 116 try { 117 mContext = context.createPackageContextAsUser(context.getPackageName(), 0, user); 118 } catch (PackageManager.NameNotFoundException doesNotHappen) { 119 throw new IllegalStateException(); 120 } 121 } 122 123 /** 124 * Forward parser and skip everything up to the end of the current tag. 125 * 126 * @param parser The parser to forward 127 */ skipToEndOfTag(@onNull XmlPullParser parser)128 private static void skipToEndOfTag(@NonNull XmlPullParser parser) 129 throws IOException, XmlPullParserException { 130 int numOpenTags = 1; 131 while (numOpenTags > 0) { 132 switch (parser.next()) { 133 case START_TAG: 134 numOpenTags++; 135 break; 136 case END_TAG: 137 numOpenTags--; 138 break; 139 default: 140 // ignore 141 } 142 } 143 } 144 145 /** 146 * Forward parser to a given direct sub-tag. 147 * 148 * @param parser The parser to forward 149 * @param tag The tag to search for 150 */ skipToTag(@onNull XmlPullParser parser, @NonNull String tag)151 private void skipToTag(@NonNull XmlPullParser parser, @NonNull String tag) 152 throws IOException, XmlPullParserException { 153 int type; 154 do { 155 type = parser.next(); 156 157 switch (type) { 158 case START_TAG: 159 if (!parser.getName().equals(tag)) { 160 skipToEndOfTag(parser); 161 } 162 163 return; 164 } 165 } while (type != END_DOCUMENT); 166 } 167 168 /** 169 * Read a XML file and return the packages stored in it. 170 * 171 * @param parser The file to read 172 * 173 * @return The packages in this file 174 */ parseFromXml(@onNull XmlPullParser parser)175 private @NonNull ArrayList<BackupPackageState> parseFromXml(@NonNull XmlPullParser parser) 176 throws IOException, XmlPullParserException { 177 ArrayList<BackupPackageState> pkgStates = new ArrayList<>(); 178 179 skipToTag(parser, TAG_PERMISSION_BACKUP); 180 181 int backupPlatformVersion; 182 try { 183 backupPlatformVersion = Integer.parseInt( 184 parser.getAttributeValue(null, ATTR_PLATFORM_VERSION)); 185 } catch (NumberFormatException ignored) { 186 // Platforms P and before did not store the platform version 187 backupPlatformVersion = Build.VERSION_CODES.P; 188 } 189 190 skipToTag(parser, TAG_ALL_GRANTS); 191 192 if (parser.getEventType() != START_TAG && !parser.getName().equals(TAG_ALL_GRANTS)) { 193 throw new XmlPullParserException("Could not find " + TAG_PERMISSION_BACKUP + " > " 194 + TAG_ALL_GRANTS); 195 } 196 197 // Read packages to restore from xml 198 int type; 199 do { 200 type = parser.next(); 201 202 switch (type) { 203 case START_TAG: 204 switch (parser.getName()) { 205 case TAG_GRANT: 206 try { 207 pkgStates.add(BackupPackageState.parseFromXml(parser, mContext, 208 backupPlatformVersion)); 209 } catch (XmlPullParserException e) { 210 Log.e(LOG_TAG, "Could not parse permissions ", e); 211 skipToEndOfTag(parser); 212 } 213 break; 214 default: 215 // ignore tag 216 Log.w(LOG_TAG, "Found unexpected tag " + parser.getName() 217 + " during restore"); 218 skipToEndOfTag(parser); 219 } 220 } 221 } while (type != END_DOCUMENT); 222 223 return pkgStates; 224 } 225 226 /** 227 * Try to restore the permission state from XML. 228 * 229 * <p>If some apps could not be restored, the leftover apps are written to 230 * {@link Constants#DELAYED_RESTORE_PERMISSIONS_FILE}. 231 * 232 * @param parser The xml to read 233 */ restoreState(@onNull XmlPullParser parser)234 void restoreState(@NonNull XmlPullParser parser) throws IOException, XmlPullParserException { 235 ArrayList<BackupPackageState> pkgStates = parseFromXml(parser); 236 237 ArrayList<BackupPackageState> packagesToRestoreLater = new ArrayList<>(); 238 int numPkgStates = pkgStates.size(); 239 if (numPkgStates > 0) { 240 // Try to restore packages 241 for (int i = 0; i < numPkgStates; i++) { 242 BackupPackageState pkgState = pkgStates.get(i); 243 244 PackageInfo pkgInfo; 245 try { 246 pkgInfo = mContext.getPackageManager().getPackageInfo(pkgState.mPackageName, 247 GET_PERMISSIONS | GET_SIGNING_CERTIFICATES); 248 } catch (PackageManager.NameNotFoundException ignored) { 249 packagesToRestoreLater.add(pkgState); 250 continue; 251 } 252 253 if (!checkCertificateDigestsMatch(pkgInfo, pkgState)) { 254 continue; 255 } 256 257 pkgState.restore(mContext, pkgInfo); 258 } 259 } 260 261 synchronized (sLock) { 262 writeDelayedStorePkgsLocked(packagesToRestoreLater); 263 } 264 } 265 266 /** 267 * Returns whether the backed up package and the package being restored have compatible signing 268 * certificate digests. 269 * 270 * <p> Permissions should only be restored if the backed up package has the same signing 271 * certificate(s) or an ancestor (in the case of certification rotation). 272 * 273 * <p>If no certificates are found stored for the backed up package, we return true anyway as 274 * certificate storage does not exist before {@link Build.VERSION_CODES.TIRAMISU}. 275 */ checkCertificateDigestsMatch( @onNull PackageInfo packageToRestoreInfo, @NonNull BackupPackageState backupPackageState)276 private boolean checkCertificateDigestsMatch( 277 @NonNull PackageInfo packageToRestoreInfo, 278 @NonNull BackupPackageState backupPackageState) { 279 // No signing information was stored for the backed up app. 280 if (backupPackageState.mBackupSigningInfoState == null) { 281 return true; 282 } 283 284 // The backed up app was unsigned. 285 if (backupPackageState.mBackupSigningInfoState.mCurrentCertDigests.isEmpty()) { 286 return false; 287 } 288 289 // We don't have signing information for the restored app, but the backed up app was signed. 290 if (packageToRestoreInfo.signingInfo == null) { 291 return false; 292 } 293 294 // The restored app is unsigned. 295 if (packageToRestoreInfo.signingInfo.getApkContentsSigners() == null 296 || packageToRestoreInfo.signingInfo.getApkContentsSigners().length == 0) { 297 return false; 298 } 299 300 // If the restored app is a system app, we allow permissions to be restored without any 301 // certificate checks. 302 // System apps are signed with the device's platform certificate, so on 303 // different phones the same system app can have different certificates. 304 // We perform this check to be consistent with the Backup and Restore feature logic in 305 // frameworks/base/services/core/java/com/android/server/backup/BackupUtils.java 306 if ((packageToRestoreInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 307 return true; 308 } 309 310 // Both backed up app and restored app have signing information, so we check that these are 311 // compatible for the purpose of restoring permissions to the restored app. 312 return hasCompatibleSignaturesForRestore(packageToRestoreInfo.signingInfo, 313 backupPackageState.mBackupSigningInfoState); 314 } 315 316 /** 317 * Write a xml file for the given packages. 318 * 319 * @param serializer The file to write to 320 * @param pkgs The packages to write 321 */ writePkgsAsXml(@onNull XmlSerializer serializer, @NonNull ArrayList<BackupPackageState> pkgs)322 private static void writePkgsAsXml(@NonNull XmlSerializer serializer, 323 @NonNull ArrayList<BackupPackageState> pkgs) throws IOException { 324 serializer.startDocument(null, true); 325 326 serializer.startTag(null, TAG_PERMISSION_BACKUP); 327 328 if (BuildCompat.isAtLeastQ()) { 329 // STOPSHIP: Remove compatibility code once Q SDK level is declared 330 serializer.attribute(null, ATTR_PLATFORM_VERSION, 331 Integer.valueOf(Build.VERSION_CODES.Q).toString()); 332 } else { 333 serializer.attribute(null, ATTR_PLATFORM_VERSION, 334 Integer.valueOf(Build.VERSION.SDK_INT).toString()); 335 } 336 337 serializer.startTag(null, TAG_ALL_GRANTS); 338 339 int numPkgs = pkgs.size(); 340 for (int i = 0; i < numPkgs; i++) { 341 BackupPackageState packageState = pkgs.get(i); 342 343 if (packageState != null) { 344 packageState.writeAsXml(serializer); 345 } 346 } 347 348 serializer.endTag(null, TAG_ALL_GRANTS); 349 serializer.endTag(null, TAG_PERMISSION_BACKUP); 350 351 serializer.endDocument(); 352 } 353 354 /** 355 * Update the {@link Constants#DELAYED_RESTORE_PERMISSIONS_FILE} to contain the 356 * {@code packagesToRestoreLater}. 357 * 358 * @param packagesToRestoreLater The new pkgs in the delayed restore file 359 */ writeDelayedStorePkgsLocked( @onNull ArrayList<BackupPackageState> packagesToRestoreLater)360 private void writeDelayedStorePkgsLocked( 361 @NonNull ArrayList<BackupPackageState> packagesToRestoreLater) { 362 try (OutputStream delayedRestoreData = mContext.openFileOutput( 363 DELAYED_RESTORE_PERMISSIONS_FILE, MODE_PRIVATE)) { 364 XmlSerializer serializer = newSerializer(); 365 serializer.setOutput(delayedRestoreData, UTF_8.name()); 366 367 writePkgsAsXml(serializer, packagesToRestoreLater); 368 serializer.flush(); 369 } catch (IOException e) { 370 Log.e(LOG_TAG, "Could not remember which packages still need to be restored", e); 371 } 372 } 373 374 /** 375 * Write the state of all packages as XML. 376 * 377 * @param serializer The xml to write to 378 */ writeState(@onNull XmlSerializer serializer)379 void writeState(@NonNull XmlSerializer serializer) throws IOException { 380 List<PackageInfo> pkgs = mContext.getPackageManager().getInstalledPackages( 381 GET_PERMISSIONS | GET_SIGNING_CERTIFICATES); 382 ArrayList<BackupPackageState> backupPkgs = new ArrayList<>(); 383 384 int numPkgs = pkgs.size(); 385 for (int i = 0; i < numPkgs; i++) { 386 BackupPackageState packageState = BackupPackageState.fromAppPermissions(mContext, 387 pkgs.get(i)); 388 389 if (packageState != null) { 390 backupPkgs.add(packageState); 391 } 392 } 393 394 writePkgsAsXml(serializer, backupPkgs); 395 } 396 397 /** 398 * Restore delayed permission state for a package (if delayed during {@link #restoreState}). 399 * 400 * @param packageName The package to be restored 401 * 402 * @return {@code true} if there is still delayed backup left 403 */ restoreDelayedState(@onNull String packageName)404 boolean restoreDelayedState(@NonNull String packageName) { 405 synchronized (sLock) { 406 ArrayList<BackupPackageState> packagesToRestoreLater; 407 408 try (FileInputStream delayedRestoreData = 409 mContext.openFileInput(DELAYED_RESTORE_PERMISSIONS_FILE)) { 410 XmlPullParser parser = Xml.newPullParser(); 411 parser.setInput(delayedRestoreData, UTF_8.name()); 412 413 packagesToRestoreLater = parseFromXml(parser); 414 } catch (IOException | XmlPullParserException e) { 415 Log.e(LOG_TAG, "Could not parse delayed permissions", e); 416 return false; 417 } 418 419 PackageInfo pkgInfo = null; 420 try { 421 pkgInfo = mContext.getPackageManager().getPackageInfo( 422 packageName, GET_PERMISSIONS | GET_SIGNING_CERTIFICATES); 423 } catch (PackageManager.NameNotFoundException e) { 424 Log.e(LOG_TAG, "Could not restore delayed permissions for " + packageName, e); 425 } 426 427 if (pkgInfo != null) { 428 int numPkgs = packagesToRestoreLater.size(); 429 for (int i = 0; i < numPkgs; i++) { 430 BackupPackageState pkgState = packagesToRestoreLater.get(i); 431 432 if (pkgState.mPackageName.equals(packageName) && checkCertificateDigestsMatch( 433 pkgInfo, pkgState)) { 434 pkgState.restore(mContext, pkgInfo); 435 packagesToRestoreLater.remove(i); 436 437 writeDelayedStorePkgsLocked(packagesToRestoreLater); 438 439 break; 440 } 441 } 442 } 443 444 return packagesToRestoreLater.size() > 0; 445 } 446 } 447 448 /** 449 * State that needs to be backed up for a permission. 450 */ 451 private static class BackupPermissionState { 452 @NonNull 453 private final String mPermissionName; 454 private final boolean mIsGranted; 455 private final boolean mIsUserSet; 456 private final boolean mIsUserFixed; 457 private final boolean mWasReviewed; 458 BackupPermissionState(@onNull String permissionName, boolean isGranted, boolean isUserSet, boolean isUserFixed, boolean wasReviewed)459 private BackupPermissionState(@NonNull String permissionName, boolean isGranted, 460 boolean isUserSet, boolean isUserFixed, boolean wasReviewed) { 461 mPermissionName = permissionName; 462 mIsGranted = isGranted; 463 mIsUserSet = isUserSet; 464 mIsUserFixed = isUserFixed; 465 mWasReviewed = wasReviewed; 466 } 467 468 /** 469 * Parse a package state from XML. 470 * 471 * @param parser The data to read 472 * @param context a context to use 473 * @param backupPlatformVersion The platform version the backup was created on 474 * 475 * @return The state 476 */ 477 @NonNull parseFromXml(@onNull XmlPullParser parser, @NonNull Context context, int backupPlatformVersion)478 static List<BackupPermissionState> parseFromXml(@NonNull XmlPullParser parser, 479 @NonNull Context context, int backupPlatformVersion) 480 throws XmlPullParserException { 481 String permName = parser.getAttributeValue(null, ATTR_PERMISSION_NAME); 482 if (permName == null) { 483 throw new XmlPullParserException("Found " + TAG_PERMISSION + " without " 484 + ATTR_PERMISSION_NAME); 485 } 486 487 ArrayList<String> expandedPermissions = new ArrayList<>(); 488 expandedPermissions.add(permName); 489 490 List<SplitPermissionInfo> splitPerms = context.getSystemService( 491 PermissionManager.class).getSplitPermissions(); 492 493 // Expand the properties to permissions that were split between the platform version the 494 // backup was taken and the current version. 495 int numSplitPerms = splitPerms.size(); 496 for (int i = 0; i < numSplitPerms; i++) { 497 SplitPermissionInfo splitPerm = splitPerms.get(i); 498 if (backupPlatformVersion < splitPerm.getTargetSdk() 499 && permName.equals(splitPerm.getSplitPermission())) { 500 expandedPermissions.addAll(splitPerm.getNewPermissions()); 501 } 502 } 503 504 ArrayList<BackupPermissionState> parsedPermissions = new ArrayList<>( 505 expandedPermissions.size()); 506 int numExpandedPerms = expandedPermissions.size(); 507 for (int i = 0; i < numExpandedPerms; i++) { 508 parsedPermissions.add(new BackupPermissionState(expandedPermissions.get(i), 509 "true".equals(parser.getAttributeValue(null, ATTR_IS_GRANTED)), 510 "true".equals(parser.getAttributeValue(null, ATTR_USER_SET)), 511 "true".equals(parser.getAttributeValue(null, ATTR_USER_FIXED)), 512 "true".equals(parser.getAttributeValue(null, ATTR_WAS_REVIEWED)))); 513 } 514 515 return parsedPermissions; 516 } 517 518 /** 519 * Is the permission granted, also considering the app-op. 520 * 521 * <p>This does not consider the review-required state of the permission. 522 * 523 * @param perm The permission that might be granted 524 * 525 * @return {@code true} iff the permission and app-op is granted 526 */ isPermGrantedIncludingAppOp(@onNull Permission perm)527 private static boolean isPermGrantedIncludingAppOp(@NonNull Permission perm) { 528 return perm.isGranted() && (!perm.affectsAppOp() || perm.isAppOpAllowed()); 529 } 530 531 /** 532 * Get the state of a permission to back up. 533 * 534 * @param perm The permission to back up 535 * @param appSupportsRuntimePermissions If the app supports runtimePermissions 536 * 537 * @return The state to back up or {@code null} if the permission does not need to be 538 * backed up. 539 */ 540 @Nullable fromPermission(@onNull Permission perm, boolean appSupportsRuntimePermissions)541 private static BackupPermissionState fromPermission(@NonNull Permission perm, 542 boolean appSupportsRuntimePermissions) { 543 int grantFlags = perm.getFlags(); 544 545 if ((grantFlags & SYSTEM_RUNTIME_GRANT_MASK) != 0) { 546 return null; 547 } 548 549 if (!perm.isUserSet() && perm.isGrantedByDefault()) { 550 return null; 551 } 552 553 boolean permissionWasReviewed; 554 boolean isNotInDefaultGrantState; 555 if (appSupportsRuntimePermissions) { 556 isNotInDefaultGrantState = isPermGrantedIncludingAppOp(perm); 557 permissionWasReviewed = false; 558 } else { 559 isNotInDefaultGrantState = !isPermGrantedIncludingAppOp(perm); 560 permissionWasReviewed = !perm.isReviewRequired(); 561 } 562 563 if (isNotInDefaultGrantState || perm.isUserSet() || perm.isUserFixed() 564 || permissionWasReviewed) { 565 return new BackupPermissionState(perm.getName(), isPermGrantedIncludingAppOp(perm), 566 perm.isUserSet(), perm.isUserFixed(), permissionWasReviewed); 567 } else { 568 return null; 569 } 570 } 571 572 /** 573 * Get the states of all permissions of a group to back up. 574 * 575 * @param group The group of the permissions to back up 576 * 577 * @return The state to back up. Empty list if no permissions in the group need to be backed 578 * up 579 */ 580 @NonNull fromPermissionGroup( @onNull AppPermissionGroup group)581 static ArrayList<BackupPermissionState> fromPermissionGroup( 582 @NonNull AppPermissionGroup group) { 583 ArrayList<BackupPermissionState> permissionsToRestore = new ArrayList<>(); 584 List<Permission> perms = group.getPermissions(); 585 586 boolean appSupportsRuntimePermissions = 587 group.getApp().applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M; 588 589 int numPerms = perms.size(); 590 for (int i = 0; i < numPerms; i++) { 591 BackupPermissionState permState = fromPermission(perms.get(i), 592 appSupportsRuntimePermissions); 593 if (permState != null) { 594 permissionsToRestore.add(permState); 595 } 596 } 597 598 return permissionsToRestore; 599 } 600 601 /** 602 * Write this state as XML. 603 * 604 * @param serializer The file to write to 605 */ writeAsXml(@onNull XmlSerializer serializer)606 void writeAsXml(@NonNull XmlSerializer serializer) throws IOException { 607 serializer.startTag(null, TAG_PERMISSION); 608 609 serializer.attribute(null, ATTR_PERMISSION_NAME, mPermissionName); 610 611 if (mIsGranted) { 612 serializer.attribute(null, ATTR_IS_GRANTED, "true"); 613 } 614 615 if (mIsUserSet) { 616 serializer.attribute(null, ATTR_USER_SET, "true"); 617 } 618 619 if (mIsUserFixed) { 620 serializer.attribute(null, ATTR_USER_FIXED, "true"); 621 } 622 623 if (mWasReviewed) { 624 serializer.attribute(null, ATTR_WAS_REVIEWED, "true"); 625 } 626 627 serializer.endTag(null, TAG_PERMISSION); 628 } 629 630 /** 631 * Restore this permission state. 632 * 633 * @param appPerms The {@link AppPermissions} to restore the state to 634 * @param restoreBackgroundPerms if {@code true} only restore background permissions, 635 * if {@code false} do not restore background permissions 636 */ restore(@onNull AppPermissions appPerms, boolean restoreBackgroundPerms)637 void restore(@NonNull AppPermissions appPerms, boolean restoreBackgroundPerms) { 638 AppPermissionGroup group = appPerms.getGroupForPermission(mPermissionName); 639 if (group == null) { 640 Log.w(LOG_TAG, "Could not find group for " + mPermissionName + " in " 641 + appPerms.getPackageInfo().packageName); 642 return; 643 } 644 645 if (restoreBackgroundPerms != group.isBackgroundGroup()) { 646 return; 647 } 648 649 Permission perm = group.getPermission(mPermissionName); 650 if (mWasReviewed) { 651 perm.unsetReviewRequired(); 652 } 653 654 // Don't grant or revoke fixed permission groups 655 if (group.isSystemFixed() || group.isPolicyFixed()) { 656 return; 657 } 658 659 if (!perm.isUserSet()) { 660 if (mIsGranted) { 661 group.grantRuntimePermissions(false, mIsUserFixed, 662 new String[]{mPermissionName}); 663 } else { 664 group.revokeRuntimePermissions(mIsUserFixed, 665 new String[]{mPermissionName}); 666 } 667 668 perm.setUserSet(mIsUserSet); 669 } 670 } 671 } 672 673 /** Signing certificate information for a backed up package. */ 674 private static class BackupSigningInfoState { 675 @NonNull 676 private final Set<byte[]> mCurrentCertDigests; 677 @NonNull 678 private final Set<byte[]> mPastCertDigests; 679 BackupSigningInfoState(@onNull Set<byte[]> currentCertDigests, @NonNull Set<byte[]> pastCertDigests)680 private BackupSigningInfoState(@NonNull Set<byte[]> currentCertDigests, 681 @NonNull Set<byte[]> pastCertDigests) { 682 mCurrentCertDigests = currentCertDigests; 683 mPastCertDigests = pastCertDigests; 684 } 685 686 /** 687 * Write this state as XML. 688 * 689 * @param serializer the file to write to 690 */ writeAsXml(@onNull XmlSerializer serializer)691 void writeAsXml(@NonNull XmlSerializer serializer) throws IOException { 692 serializer.startTag(null, TAG_SIGNING_INFO); 693 694 for (byte[] digest : mCurrentCertDigests) { 695 serializer.startTag(null, TAG_CURRENT_CERTIFICATE); 696 serializer.attribute( 697 null, ATTR_CERTIFICATE_DIGEST, 698 Base64.encodeToString(digest, Base64.NO_WRAP)); 699 serializer.endTag(null, TAG_CURRENT_CERTIFICATE); 700 } 701 702 for (byte[] digest : mPastCertDigests) { 703 serializer.startTag(null, TAG_PAST_CERTIFICATE); 704 serializer.attribute( 705 null, ATTR_CERTIFICATE_DIGEST, 706 Base64.encodeToString(digest, Base64.NO_WRAP)); 707 serializer.endTag(null, TAG_PAST_CERTIFICATE); 708 } 709 710 serializer.endTag(null, TAG_SIGNING_INFO); 711 } 712 713 /** 714 * Parse the signing information state from XML. 715 * 716 * @param parser the data to read 717 * 718 * @return the signing information state 719 */ 720 @NonNull parseFromXml(@onNull XmlPullParser parser)721 static BackupSigningInfoState parseFromXml(@NonNull XmlPullParser parser) 722 throws IOException, XmlPullParserException { 723 Set<byte[]> currentCertDigests = new HashSet<>(); 724 Set<byte[]> pastCertDigests = new HashSet<>(); 725 726 while (true) { 727 switch (parser.next()) { 728 case START_TAG: 729 switch (parser.getName()) { 730 case TAG_CURRENT_CERTIFICATE: 731 String currentCertDigest = 732 parser.getAttributeValue( 733 null, ATTR_CERTIFICATE_DIGEST); 734 if (currentCertDigest == null) { 735 throw new XmlPullParserException( 736 "Found " + TAG_CURRENT_CERTIFICATE + " without " 737 + ATTR_CERTIFICATE_DIGEST); 738 } 739 currentCertDigests.add( 740 Base64.decode(currentCertDigest, Base64.NO_WRAP)); 741 skipToEndOfTag(parser); 742 break; 743 case TAG_PAST_CERTIFICATE: 744 String pastCertDigest = 745 parser.getAttributeValue( 746 null, ATTR_CERTIFICATE_DIGEST); 747 if (pastCertDigest == null) { 748 throw new XmlPullParserException( 749 "Found " + TAG_PAST_CERTIFICATE + " without " 750 + ATTR_CERTIFICATE_DIGEST); 751 } 752 pastCertDigests.add( 753 Base64.decode(pastCertDigest, Base64.NO_WRAP)); 754 skipToEndOfTag(parser); 755 break; 756 default: 757 Log.w(LOG_TAG, "Found unexpected tag " + parser.getName()); 758 skipToEndOfTag(parser); 759 } 760 761 break; 762 case END_TAG: 763 return new BackupSigningInfoState( 764 currentCertDigests, 765 pastCertDigests); 766 default: 767 throw new XmlPullParserException("Could not parse signing info"); 768 } 769 } 770 } 771 772 /** 773 * Construct the signing information state from a {@link SigningInfo} instance. 774 * 775 * @param signingInfo the {@link SigningInfo} instance 776 * 777 * @return the state 778 */ 779 @NonNull fromSigningInfo(@onNull SigningInfo signingInfo)780 static BackupSigningInfoState fromSigningInfo(@NonNull SigningInfo signingInfo) { 781 Set<byte[]> currentCertDigests = new HashSet<>(); 782 Set<byte[]> pastCertDigests = new HashSet<>(); 783 784 Signature[] apkContentsSigners = signingInfo.getApkContentsSigners(); 785 for (int i = 0; i < apkContentsSigners.length; i++) { 786 currentCertDigests.add( 787 computeSha256DigestBytes(apkContentsSigners[i].toByteArray())); 788 } 789 790 if (signingInfo.hasPastSigningCertificates()) { 791 Signature[] signingCertificateHistory = signingInfo.getSigningCertificateHistory(); 792 for (int i = 0; i < signingCertificateHistory.length; i++) { 793 pastCertDigests.add( 794 computeSha256DigestBytes(signingCertificateHistory[i].toByteArray())); 795 } 796 } 797 798 return new BackupSigningInfoState(currentCertDigests, pastCertDigests); 799 } 800 } 801 802 /** 803 * State that needs to be backed up for a package. 804 */ 805 private static class BackupPackageState { 806 @NonNull 807 final String mPackageName; 808 @NonNull 809 private final ArrayList<BackupPermissionState> mPermissionsToRestore; 810 @Nullable 811 private final BackupSigningInfoState mBackupSigningInfoState; 812 BackupPackageState( @onNull String packageName, @NonNull ArrayList<BackupPermissionState> permissionsToRestore, @Nullable BackupSigningInfoState backupSigningInfoState)813 private BackupPackageState( 814 @NonNull String packageName, 815 @NonNull ArrayList<BackupPermissionState> permissionsToRestore, 816 @Nullable BackupSigningInfoState backupSigningInfoState) { 817 mPackageName = packageName; 818 mPermissionsToRestore = permissionsToRestore; 819 mBackupSigningInfoState = backupSigningInfoState; 820 } 821 822 /** 823 * Parse a package state from XML. 824 * 825 * @param parser The data to read 826 * @param context a context to use 827 * @param backupPlatformVersion The platform version the backup was created on 828 * 829 * @return The state 830 */ 831 @NonNull parseFromXml(@onNull XmlPullParser parser, @NonNull Context context, int backupPlatformVersion)832 static BackupPackageState parseFromXml(@NonNull XmlPullParser parser, 833 @NonNull Context context, int backupPlatformVersion) 834 throws IOException, XmlPullParserException { 835 String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME); 836 if (packageName == null) { 837 throw new XmlPullParserException("Found " + TAG_GRANT + " without " 838 + ATTR_PACKAGE_NAME); 839 } 840 841 ArrayList<BackupPermissionState> permissionsToRestore = new ArrayList<>(); 842 BackupSigningInfoState signingInfo = null; 843 844 while (true) { 845 switch (parser.next()) { 846 case START_TAG: 847 switch (parser.getName()) { 848 case TAG_PERMISSION: 849 try { 850 permissionsToRestore.addAll( 851 BackupPermissionState.parseFromXml(parser, context, 852 backupPlatformVersion)); 853 } catch (XmlPullParserException e) { 854 Log.e(LOG_TAG, "Could not parse permission for " 855 + packageName, e); 856 } 857 858 skipToEndOfTag(parser); 859 break; 860 case TAG_SIGNING_INFO: 861 try { 862 signingInfo = BackupSigningInfoState.parseFromXml(parser); 863 } catch (XmlPullParserException e) { 864 Log.e(LOG_TAG, "Could not parse signing info for " 865 + packageName, e); 866 skipToEndOfTag(parser); 867 } 868 869 break; 870 default: 871 // ignore tag 872 Log.w(LOG_TAG, "Found unexpected tag " + parser.getName() 873 + " while restoring " + packageName); 874 skipToEndOfTag(parser); 875 } 876 877 break; 878 case END_TAG: 879 return new BackupPackageState( 880 packageName, 881 permissionsToRestore, 882 signingInfo); 883 case END_DOCUMENT: 884 throw new XmlPullParserException("Could not parse state for " 885 + packageName); 886 } 887 } 888 } 889 890 /** 891 * Get the state of a package to back up. 892 * 893 * @param context A context to use 894 * @param pkgInfo The package to back up. 895 * 896 * @return The state to back up or {@code null} if no permission of the package need to be 897 * backed up. 898 */ 899 @Nullable fromAppPermissions(@onNull Context context, @NonNull PackageInfo pkgInfo)900 static BackupPackageState fromAppPermissions(@NonNull Context context, 901 @NonNull PackageInfo pkgInfo) { 902 AppPermissions appPerms = new AppPermissions(context, pkgInfo, false, null); 903 904 ArrayList<BackupPermissionState> permissionsToRestore = new ArrayList<>(); 905 List<AppPermissionGroup> groups = appPerms.getPermissionGroups(); 906 907 int numGroups = groups.size(); 908 for (int groupNum = 0; groupNum < numGroups; groupNum++) { 909 AppPermissionGroup group = groups.get(groupNum); 910 911 permissionsToRestore.addAll(BackupPermissionState.fromPermissionGroup(group)); 912 913 // Background permissions are in a subgroup that is not part of 914 // {@link AppPermission#getPermissionGroups}. Hence add it explicitly here. 915 if (group.getBackgroundPermissions() != null) { 916 permissionsToRestore.addAll(BackupPermissionState.fromPermissionGroup( 917 group.getBackgroundPermissions())); 918 } 919 } 920 921 if (permissionsToRestore.size() == 0) { 922 return null; 923 } 924 925 BackupSigningInfoState signingInfoState = null; 926 927 if (pkgInfo.signingInfo != null) { 928 signingInfoState = BackupSigningInfoState.fromSigningInfo(pkgInfo.signingInfo); 929 } 930 931 return new BackupPackageState( 932 pkgInfo.packageName, permissionsToRestore, signingInfoState); 933 } 934 935 /** 936 * Write this state as XML. 937 * 938 * @param serializer The file to write to 939 */ writeAsXml(@onNull XmlSerializer serializer)940 void writeAsXml(@NonNull XmlSerializer serializer) throws IOException { 941 if (mPermissionsToRestore.size() == 0) { 942 return; 943 } 944 945 serializer.startTag(null, TAG_GRANT); 946 serializer.attribute(null, ATTR_PACKAGE_NAME, mPackageName); 947 948 int numPerms = mPermissionsToRestore.size(); 949 for (int i = 0; i < numPerms; i++) { 950 mPermissionsToRestore.get(i).writeAsXml(serializer); 951 } 952 953 if (mBackupSigningInfoState != null) { 954 mBackupSigningInfoState.writeAsXml(serializer); 955 } 956 957 serializer.endTag(null, TAG_GRANT); 958 } 959 960 /** 961 * Restore this package state. 962 * 963 * @param context A context to use 964 * @param pkgInfo The package to restore. 965 */ restore(@onNull Context context, @NonNull PackageInfo pkgInfo)966 void restore(@NonNull Context context, @NonNull PackageInfo pkgInfo) { 967 AppPermissions appPerms = new AppPermissions(context, pkgInfo, false, true, null); 968 969 ArraySet<String> affectedPermissions = new ArraySet<>(); 970 // Restore background permissions after foreground permissions as for pre-M apps bg 971 // granted and fg revoked cannot be expressed. 972 int numPerms = mPermissionsToRestore.size(); 973 for (int i = 0; i < numPerms; i++) { 974 mPermissionsToRestore.get(i).restore(appPerms, false); 975 affectedPermissions.add(mPermissionsToRestore.get(i).mPermissionName); 976 } 977 for (int i = 0; i < numPerms; i++) { 978 mPermissionsToRestore.get(i).restore(appPerms, true); 979 } 980 981 int numGroups = appPerms.getPermissionGroups().size(); 982 for (int i = 0; i < numGroups; i++) { 983 AppPermissionGroup group = appPerms.getPermissionGroups().get(i); 984 985 // Only denied groups can be user fixed 986 if (group.areRuntimePermissionsGranted()) { 987 group.setUserFixed(false); 988 } 989 990 AppPermissionGroup bgGroup = group.getBackgroundPermissions(); 991 if (bgGroup != null) { 992 // Only denied groups can be user fixed 993 if (bgGroup.areRuntimePermissionsGranted()) { 994 bgGroup.setUserFixed(false); 995 } 996 } 997 } 998 999 appPerms.persistChanges(true, affectedPermissions); 1000 } 1001 } 1002 1003 /** 1004 * Returns whether the signing certificates of the restored app and backed up app are 1005 * compatible for the restored app to be granted the backed up app's permissions. 1006 * 1007 * <p>This returns true when any one of the following is true: 1008 * 1009 * <ul> 1010 * <li> the backed up app has multiple signing certificates and the restored app 1011 * has identical multiple signing certificates 1012 * <li> the backed up app has a single signing certificate and it is the current 1013 * single signing certificate of the restored app 1014 * <li> the backed up app has a single signing certificate and it is present in the 1015 * signing certificate history of the restored app 1016 * <li> the backed up app has a single signing certificate and signing certificate 1017 * history, and the signing certificate of the restored app is present in that history 1018 * </ul>* 1019 */ hasCompatibleSignaturesForRestore(@onNull SigningInfo restoredSigningInfo, @NonNull BackupSigningInfoState backupSigningInfoState)1020 private boolean hasCompatibleSignaturesForRestore(@NonNull SigningInfo restoredSigningInfo, 1021 @NonNull BackupSigningInfoState backupSigningInfoState) { 1022 Set<byte[]> backupCertDigests = backupSigningInfoState.mCurrentCertDigests; 1023 Set<byte[]> backupPastCertDigests = backupSigningInfoState.mPastCertDigests; 1024 Signature[] restoredSignatures = restoredSigningInfo.getApkContentsSigners(); 1025 1026 // Check that both apps have the same number of signing certificates. This will be a 1027 // required check for both the single and multiple certificate cases. 1028 if (backupCertDigests.size() != restoredSignatures.length) { 1029 return false; 1030 } 1031 1032 Set<byte[]> restoredCertDigests = new HashSet<>(); 1033 for (Signature signature: restoredSignatures) { 1034 restoredCertDigests.add(computeSha256DigestBytes(signature.toByteArray())); 1035 } 1036 1037 // If the backed up app has multiple signing certificates, the restored app should be 1038 // signed by that exact set of multiple signing certificates. 1039 if (backupCertDigests.size() > 1) { 1040 // Check that the restored certificates are a subset of the backed up certificates. 1041 if (!CollectionUtils.containsSubset(backupCertDigests, restoredCertDigests)) { 1042 return false; 1043 } 1044 // Check that the backed up certificates are a subset of the restored certificates. 1045 if (!CollectionUtils.containsSubset(restoredCertDigests, backupCertDigests)) { 1046 return false; 1047 } 1048 return true; 1049 } 1050 1051 // If both apps have a single signing certificate, we check if they are equal or if one 1052 // app's certificate is in the signing certificate history of the other. 1053 byte[] backupCertDigest = backupCertDigests.iterator().next(); 1054 byte[] restoredPastCertDigest = restoredCertDigests.iterator().next(); 1055 1056 // Check if the backed up app and restored app have the same signing certificate. 1057 if (Arrays.equals(backupCertDigest, restoredPastCertDigest)) { 1058 return true; 1059 } 1060 1061 // Check if the restored app's certificate is in the backed up app's signing certificate 1062 // history. 1063 if (CollectionUtils.contains(backupPastCertDigests, restoredPastCertDigest)) { 1064 return true; 1065 } 1066 1067 // Check if the backed up app's certificate is in the restored app's signing certificate 1068 // history. 1069 if (restoredSigningInfo.hasPastSigningCertificates()) { 1070 // The last element in the pastSigningCertificates array is the current signer; 1071 // since that was verified above, just check all the signers in the lineage. 1072 for (int i = 0; i < restoredSigningInfo.getSigningCertificateHistory().length - 1; 1073 i++) { 1074 restoredPastCertDigest = computeSha256DigestBytes( 1075 restoredSigningInfo.getSigningCertificateHistory()[i].toByteArray()); 1076 if (Arrays.equals(backupCertDigest, restoredPastCertDigest)) { 1077 return true; 1078 } 1079 } 1080 } 1081 return false; 1082 } 1083 1084 /** Computes the SHA256 digest of the provided {@code byte} array. */ 1085 @Nullable computeSha256DigestBytes(@onNull byte[] data)1086 private static byte[] computeSha256DigestBytes(@NonNull byte[] data) { 1087 MessageDigest messageDigest; 1088 try { 1089 messageDigest = MessageDigest.getInstance("SHA256"); 1090 } catch (NoSuchAlgorithmException e) { 1091 return null; 1092 } 1093 1094 messageDigest.update(data); 1095 1096 return messageDigest.digest(); 1097 } 1098 } 1099