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.ArrayMap; 45 import android.util.ArraySet; 46 import android.util.Base64; 47 import android.util.Log; 48 import android.util.Xml; 49 50 import androidx.annotation.NonNull; 51 import androidx.annotation.Nullable; 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 case END_DOCUMENT: 140 return; 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 serializer.attribute(null, ATTR_PLATFORM_VERSION, 328 Integer.valueOf(Build.VERSION.SDK_INT).toString()); 329 330 serializer.startTag(null, TAG_ALL_GRANTS); 331 332 int numPkgs = pkgs.size(); 333 for (int i = 0; i < numPkgs; i++) { 334 BackupPackageState packageState = pkgs.get(i); 335 336 if (packageState != null) { 337 packageState.writeAsXml(serializer); 338 } 339 } 340 341 serializer.endTag(null, TAG_ALL_GRANTS); 342 serializer.endTag(null, TAG_PERMISSION_BACKUP); 343 344 serializer.endDocument(); 345 } 346 347 /** 348 * Update the {@link Constants#DELAYED_RESTORE_PERMISSIONS_FILE} to contain the 349 * {@code packagesToRestoreLater}. 350 * 351 * @param packagesToRestoreLater The new pkgs in the delayed restore file 352 */ writeDelayedStorePkgsLocked( @onNull ArrayList<BackupPackageState> packagesToRestoreLater)353 private void writeDelayedStorePkgsLocked( 354 @NonNull ArrayList<BackupPackageState> packagesToRestoreLater) { 355 if (packagesToRestoreLater.size() == 0) { 356 mContext.deleteFile(DELAYED_RESTORE_PERMISSIONS_FILE); 357 return; 358 } 359 try (OutputStream delayedRestoreData = mContext.openFileOutput( 360 DELAYED_RESTORE_PERMISSIONS_FILE, MODE_PRIVATE)) { 361 XmlSerializer serializer = newSerializer(); 362 serializer.setOutput(delayedRestoreData, UTF_8.name()); 363 364 writePkgsAsXml(serializer, packagesToRestoreLater); 365 serializer.flush(); 366 } catch (IOException e) { 367 Log.e(LOG_TAG, "Could not remember which packages still need to be restored", e); 368 } 369 } 370 371 /** 372 * Write the state of all packages as XML. 373 * 374 * @param serializer The xml to write to 375 */ writeState(@onNull XmlSerializer serializer)376 void writeState(@NonNull XmlSerializer serializer) throws IOException { 377 List<PackageInfo> pkgs = mContext.getPackageManager().getInstalledPackages( 378 GET_PERMISSIONS | GET_SIGNING_CERTIFICATES); 379 ArrayList<BackupPackageState> backupPkgs = new ArrayList<>(); 380 381 int numPkgs = pkgs.size(); 382 for (int i = 0; i < numPkgs; i++) { 383 BackupPackageState packageState = BackupPackageState.fromAppPermissions(mContext, 384 pkgs.get(i)); 385 386 if (packageState != null) { 387 backupPkgs.add(packageState); 388 } 389 } 390 391 writePkgsAsXml(serializer, backupPkgs); 392 } 393 394 /** 395 * Restore delayed permission state for a package (if delayed during {@link #restoreState}). 396 * 397 * @param packageName The package to be restored 398 * 399 * @return {@code true} if there is still delayed backup left 400 */ restoreDelayedState(@onNull String packageName)401 boolean restoreDelayedState(@NonNull String packageName) { 402 synchronized (sLock) { 403 ArrayList<BackupPackageState> packagesToRestoreLater; 404 405 try (FileInputStream delayedRestoreData = 406 mContext.openFileInput(DELAYED_RESTORE_PERMISSIONS_FILE)) { 407 XmlPullParser parser = Xml.newPullParser(); 408 parser.setInput(delayedRestoreData, UTF_8.name()); 409 410 packagesToRestoreLater = parseFromXml(parser); 411 } catch (IOException | XmlPullParserException e) { 412 Log.e(LOG_TAG, "Could not parse delayed permissions", e); 413 return false; 414 } 415 416 PackageInfo pkgInfo = null; 417 try { 418 pkgInfo = mContext.getPackageManager().getPackageInfo( 419 packageName, GET_PERMISSIONS | GET_SIGNING_CERTIFICATES); 420 } catch (PackageManager.NameNotFoundException e) { 421 Log.e(LOG_TAG, "Could not restore delayed permissions for " + packageName, e); 422 } 423 424 if (pkgInfo != null) { 425 int numPkgs = packagesToRestoreLater.size(); 426 for (int i = 0; i < numPkgs; i++) { 427 BackupPackageState pkgState = packagesToRestoreLater.get(i); 428 429 if (pkgState.mPackageName.equals(packageName) && checkCertificateDigestsMatch( 430 pkgInfo, pkgState)) { 431 pkgState.restore(mContext, pkgInfo); 432 packagesToRestoreLater.remove(i); 433 434 writeDelayedStorePkgsLocked(packagesToRestoreLater); 435 436 break; 437 } 438 } 439 } 440 441 return packagesToRestoreLater.size() > 0; 442 } 443 } 444 445 /** 446 * State that needs to be backed up for a permission. 447 */ 448 private static class BackupPermissionState { 449 @NonNull 450 private final String mPermissionName; 451 private final boolean mIsGranted; 452 private final boolean mIsUserSet; 453 private final boolean mIsUserFixed; 454 private final boolean mWasReviewed; 455 456 // Not persisted, used during parsing so explicitly defined state takes precedence 457 private final boolean mIsAddedFromSplit; 458 BackupPermissionState(@onNull String permissionName, boolean isGranted, boolean isUserSet, boolean isUserFixed, boolean wasReviewed, boolean isAddedFromSplit)459 private BackupPermissionState(@NonNull String permissionName, boolean isGranted, 460 boolean isUserSet, boolean isUserFixed, boolean wasReviewed, 461 boolean isAddedFromSplit) { 462 mPermissionName = permissionName; 463 mIsGranted = isGranted; 464 mIsUserSet = isUserSet; 465 mIsUserFixed = isUserFixed; 466 mWasReviewed = wasReviewed; 467 mIsAddedFromSplit = isAddedFromSplit; 468 } 469 470 /** 471 * Parse a package state from XML. 472 * 473 * @param parser The data to read 474 * @param context a context to use 475 * @param backupPlatformVersion The platform version the backup was created on 476 * 477 * @return The state 478 */ 479 @NonNull parseFromXml(@onNull XmlPullParser parser, @NonNull Context context, int backupPlatformVersion)480 static List<BackupPermissionState> parseFromXml(@NonNull XmlPullParser parser, 481 @NonNull Context context, int backupPlatformVersion) 482 throws XmlPullParserException { 483 String permName = parser.getAttributeValue(null, ATTR_PERMISSION_NAME); 484 if (permName == null) { 485 throw new XmlPullParserException("Found " + TAG_PERMISSION + " without " 486 + ATTR_PERMISSION_NAME); 487 } 488 489 ArrayList<String> expandedPermissions = new ArrayList<>(); 490 expandedPermissions.add(permName); 491 492 List<SplitPermissionInfo> splitPerms = context.getSystemService( 493 PermissionManager.class).getSplitPermissions(); 494 495 // Expand the properties to permissions that were split between the platform version the 496 // backup was taken and the current version. 497 int numSplitPerms = splitPerms.size(); 498 for (int i = 0; i < numSplitPerms; i++) { 499 SplitPermissionInfo splitPerm = splitPerms.get(i); 500 if (backupPlatformVersion < splitPerm.getTargetSdk() 501 && permName.equals(splitPerm.getSplitPermission())) { 502 expandedPermissions.addAll(splitPerm.getNewPermissions()); 503 } 504 } 505 506 ArrayList<BackupPermissionState> parsedPermissions = new ArrayList<>( 507 expandedPermissions.size()); 508 int numExpandedPerms = expandedPermissions.size(); 509 for (int i = 0; i < numExpandedPerms; i++) { 510 parsedPermissions.add(new BackupPermissionState(expandedPermissions.get(i), 511 "true".equals(parser.getAttributeValue(null, ATTR_IS_GRANTED)), 512 "true".equals(parser.getAttributeValue(null, ATTR_USER_SET)), 513 "true".equals(parser.getAttributeValue(null, ATTR_USER_FIXED)), 514 "true".equals(parser.getAttributeValue(null, ATTR_WAS_REVIEWED)), 515 /* isAddedFromSplit */ i > 0)); 516 } 517 518 return parsedPermissions; 519 } 520 521 /** 522 * Is the permission granted, also considering the app-op. 523 * 524 * <p>This does not consider the review-required state of the permission. 525 * 526 * @param perm The permission that might be granted 527 * 528 * @return {@code true} iff the permission and app-op is granted 529 */ isPermGrantedIncludingAppOp(@onNull Permission perm)530 private static boolean isPermGrantedIncludingAppOp(@NonNull Permission perm) { 531 return perm.isGranted() && (!perm.affectsAppOp() || perm.isAppOpAllowed()); 532 } 533 534 /** 535 * Get the state of a permission to back up. 536 * 537 * @param perm The permission to back up 538 * @param appSupportsRuntimePermissions If the app supports runtimePermissions 539 * 540 * @return The state to back up or {@code null} if the permission does not need to be 541 * backed up. 542 */ 543 @Nullable fromPermission(@onNull Permission perm, boolean appSupportsRuntimePermissions)544 private static BackupPermissionState fromPermission(@NonNull Permission perm, 545 boolean appSupportsRuntimePermissions) { 546 int grantFlags = perm.getFlags(); 547 548 if ((grantFlags & SYSTEM_RUNTIME_GRANT_MASK) != 0) { 549 return null; 550 } 551 552 if (!perm.isUserSet() && perm.isGrantedByDefault()) { 553 return null; 554 } 555 556 boolean permissionWasReviewed; 557 boolean isNotInDefaultGrantState; 558 if (appSupportsRuntimePermissions) { 559 isNotInDefaultGrantState = isPermGrantedIncludingAppOp(perm); 560 permissionWasReviewed = false; 561 } else { 562 isNotInDefaultGrantState = !isPermGrantedIncludingAppOp(perm); 563 permissionWasReviewed = !perm.isReviewRequired(); 564 } 565 566 if (isNotInDefaultGrantState || perm.isUserSet() || perm.isUserFixed() 567 || permissionWasReviewed) { 568 return new BackupPermissionState(perm.getName(), isPermGrantedIncludingAppOp(perm), 569 perm.isUserSet(), perm.isUserFixed(), permissionWasReviewed, 570 /* isAddedFromSplit */ false); 571 } else { 572 return null; 573 } 574 } 575 576 /** 577 * Get the states of all permissions of a group to back up. 578 * 579 * @param group The group of the permissions to back up 580 * 581 * @return The state to back up. Empty list if no permissions in the group need to be backed 582 * up 583 */ 584 @NonNull fromPermissionGroup( @onNull AppPermissionGroup group)585 static ArrayList<BackupPermissionState> fromPermissionGroup( 586 @NonNull AppPermissionGroup group) { 587 ArrayList<BackupPermissionState> permissionsToRestore = new ArrayList<>(); 588 List<Permission> perms = group.getPermissions(); 589 590 boolean appSupportsRuntimePermissions = 591 group.getApp().applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M; 592 593 int numPerms = perms.size(); 594 for (int i = 0; i < numPerms; i++) { 595 BackupPermissionState permState = fromPermission(perms.get(i), 596 appSupportsRuntimePermissions); 597 if (permState != null) { 598 permissionsToRestore.add(permState); 599 } 600 } 601 602 return permissionsToRestore; 603 } 604 605 /** 606 * Write this state as XML. 607 * 608 * @param serializer The file to write to 609 */ writeAsXml(@onNull XmlSerializer serializer)610 void writeAsXml(@NonNull XmlSerializer serializer) throws IOException { 611 serializer.startTag(null, TAG_PERMISSION); 612 613 serializer.attribute(null, ATTR_PERMISSION_NAME, mPermissionName); 614 615 if (mIsGranted) { 616 serializer.attribute(null, ATTR_IS_GRANTED, "true"); 617 } 618 619 if (mIsUserSet) { 620 serializer.attribute(null, ATTR_USER_SET, "true"); 621 } 622 623 if (mIsUserFixed) { 624 serializer.attribute(null, ATTR_USER_FIXED, "true"); 625 } 626 627 if (mWasReviewed) { 628 serializer.attribute(null, ATTR_WAS_REVIEWED, "true"); 629 } 630 631 serializer.endTag(null, TAG_PERMISSION); 632 } 633 634 /** 635 * Restore this permission state. 636 * 637 * @param appPerms The {@link AppPermissions} to restore the state to 638 * @param restoreBackgroundPerms if {@code true} only restore background permissions, 639 * if {@code false} do not restore background permissions 640 */ restore(@onNull AppPermissions appPerms, boolean restoreBackgroundPerms)641 void restore(@NonNull AppPermissions appPerms, boolean restoreBackgroundPerms) { 642 AppPermissionGroup group = appPerms.getGroupForPermission(mPermissionName); 643 if (group == null) { 644 Log.w(LOG_TAG, "Could not find group for " + mPermissionName + " in " 645 + appPerms.getPackageInfo().packageName); 646 return; 647 } 648 649 if (restoreBackgroundPerms != group.isBackgroundGroup()) { 650 return; 651 } 652 653 Permission perm = group.getPermission(mPermissionName); 654 if (mWasReviewed) { 655 perm.unsetReviewRequired(); 656 } 657 658 // Don't grant or revoke fixed permission groups 659 if (group.isSystemFixed() || group.isPolicyFixed()) { 660 return; 661 } 662 663 if (!perm.isUserSet()) { 664 if (mIsGranted) { 665 group.grantRuntimePermissions(false, mIsUserFixed, 666 new String[]{mPermissionName}); 667 } else { 668 group.revokeRuntimePermissions(mIsUserFixed, 669 new String[]{mPermissionName}); 670 } 671 672 perm.setUserSet(mIsUserSet); 673 } 674 } 675 } 676 677 /** Signing certificate information for a backed up package. */ 678 private static class BackupSigningInfoState { 679 @NonNull 680 private final Set<byte[]> mCurrentCertDigests; 681 @NonNull 682 private final Set<byte[]> mPastCertDigests; 683 BackupSigningInfoState(@onNull Set<byte[]> currentCertDigests, @NonNull Set<byte[]> pastCertDigests)684 private BackupSigningInfoState(@NonNull Set<byte[]> currentCertDigests, 685 @NonNull Set<byte[]> pastCertDigests) { 686 mCurrentCertDigests = currentCertDigests; 687 mPastCertDigests = pastCertDigests; 688 } 689 690 /** 691 * Write this state as XML. 692 * 693 * @param serializer the file to write to 694 */ writeAsXml(@onNull XmlSerializer serializer)695 void writeAsXml(@NonNull XmlSerializer serializer) throws IOException { 696 serializer.startTag(null, TAG_SIGNING_INFO); 697 698 for (byte[] digest : mCurrentCertDigests) { 699 serializer.startTag(null, TAG_CURRENT_CERTIFICATE); 700 serializer.attribute( 701 null, ATTR_CERTIFICATE_DIGEST, 702 Base64.encodeToString(digest, Base64.NO_WRAP)); 703 serializer.endTag(null, TAG_CURRENT_CERTIFICATE); 704 } 705 706 for (byte[] digest : mPastCertDigests) { 707 serializer.startTag(null, TAG_PAST_CERTIFICATE); 708 serializer.attribute( 709 null, ATTR_CERTIFICATE_DIGEST, 710 Base64.encodeToString(digest, Base64.NO_WRAP)); 711 serializer.endTag(null, TAG_PAST_CERTIFICATE); 712 } 713 714 serializer.endTag(null, TAG_SIGNING_INFO); 715 } 716 717 /** 718 * Parse the signing information state from XML. 719 * 720 * @param parser the data to read 721 * 722 * @return the signing information state 723 */ 724 @NonNull parseFromXml(@onNull XmlPullParser parser)725 static BackupSigningInfoState parseFromXml(@NonNull XmlPullParser parser) 726 throws IOException, XmlPullParserException { 727 Set<byte[]> currentCertDigests = new HashSet<>(); 728 Set<byte[]> pastCertDigests = new HashSet<>(); 729 730 while (true) { 731 switch (parser.next()) { 732 case START_TAG: 733 switch (parser.getName()) { 734 case TAG_CURRENT_CERTIFICATE: 735 String currentCertDigest = 736 parser.getAttributeValue( 737 null, ATTR_CERTIFICATE_DIGEST); 738 if (currentCertDigest == null) { 739 throw new XmlPullParserException( 740 "Found " + TAG_CURRENT_CERTIFICATE + " without " 741 + ATTR_CERTIFICATE_DIGEST); 742 } 743 currentCertDigests.add( 744 Base64.decode(currentCertDigest, Base64.NO_WRAP)); 745 skipToEndOfTag(parser); 746 break; 747 case TAG_PAST_CERTIFICATE: 748 String pastCertDigest = 749 parser.getAttributeValue( 750 null, ATTR_CERTIFICATE_DIGEST); 751 if (pastCertDigest == null) { 752 throw new XmlPullParserException( 753 "Found " + TAG_PAST_CERTIFICATE + " without " 754 + ATTR_CERTIFICATE_DIGEST); 755 } 756 pastCertDigests.add( 757 Base64.decode(pastCertDigest, Base64.NO_WRAP)); 758 skipToEndOfTag(parser); 759 break; 760 default: 761 Log.w(LOG_TAG, "Found unexpected tag " + parser.getName()); 762 skipToEndOfTag(parser); 763 } 764 765 break; 766 case END_TAG: 767 return new BackupSigningInfoState( 768 currentCertDigests, 769 pastCertDigests); 770 } 771 } 772 } 773 774 /** 775 * Construct the signing information state from a {@link SigningInfo} instance. 776 * 777 * @param signingInfo the {@link SigningInfo} instance 778 * 779 * @return the state 780 */ 781 @NonNull fromSigningInfo(@onNull SigningInfo signingInfo)782 static BackupSigningInfoState fromSigningInfo(@NonNull SigningInfo signingInfo) { 783 Set<byte[]> currentCertDigests = new HashSet<>(); 784 Set<byte[]> pastCertDigests = new HashSet<>(); 785 786 Signature[] apkContentsSigners = signingInfo.getApkContentsSigners(); 787 for (int i = 0; i < apkContentsSigners.length; i++) { 788 currentCertDigests.add( 789 computeSha256DigestBytes(apkContentsSigners[i].toByteArray())); 790 } 791 792 if (signingInfo.hasPastSigningCertificates()) { 793 Signature[] signingCertificateHistory = signingInfo.getSigningCertificateHistory(); 794 for (int i = 0; i < signingCertificateHistory.length; i++) { 795 pastCertDigests.add( 796 computeSha256DigestBytes(signingCertificateHistory[i].toByteArray())); 797 } 798 } 799 800 return new BackupSigningInfoState(currentCertDigests, pastCertDigests); 801 } 802 } 803 804 /** 805 * State that needs to be backed up for a package. 806 */ 807 private static class BackupPackageState { 808 @NonNull 809 final String mPackageName; 810 @NonNull 811 private final ArrayList<BackupPermissionState> mPermissionsToRestore; 812 @Nullable 813 private final BackupSigningInfoState mBackupSigningInfoState; 814 BackupPackageState( @onNull String packageName, @NonNull ArrayList<BackupPermissionState> permissionsToRestore, @Nullable BackupSigningInfoState backupSigningInfoState)815 private BackupPackageState( 816 @NonNull String packageName, 817 @NonNull ArrayList<BackupPermissionState> permissionsToRestore, 818 @Nullable BackupSigningInfoState backupSigningInfoState) { 819 mPackageName = packageName; 820 mPermissionsToRestore = permissionsToRestore; 821 mBackupSigningInfoState = backupSigningInfoState; 822 } 823 824 /** 825 * Parse a package state from XML. 826 * 827 * @param parser The data to read 828 * @param context a context to use 829 * @param backupPlatformVersion The platform version the backup was created on 830 * 831 * @return The state 832 */ 833 @NonNull parseFromXml(@onNull XmlPullParser parser, @NonNull Context context, int backupPlatformVersion)834 static BackupPackageState parseFromXml(@NonNull XmlPullParser parser, 835 @NonNull Context context, int backupPlatformVersion) 836 throws IOException, XmlPullParserException { 837 String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME); 838 if (packageName == null) { 839 throw new XmlPullParserException("Found " + TAG_GRANT + " without " 840 + ATTR_PACKAGE_NAME); 841 } 842 843 ArrayMap<String, BackupPermissionState> permissionsToRestore = new ArrayMap<>(); 844 BackupSigningInfoState signingInfo = null; 845 846 while (true) { 847 switch (parser.next()) { 848 case START_TAG: 849 switch (parser.getName()) { 850 case TAG_PERMISSION: 851 try { 852 addNewPermissions(permissionsToRestore, 853 BackupPermissionState.parseFromXml(parser, context, 854 backupPlatformVersion)); 855 } catch (XmlPullParserException e) { 856 Log.e(LOG_TAG, "Could not parse permission for " 857 + packageName, e); 858 } 859 860 skipToEndOfTag(parser); 861 break; 862 case TAG_SIGNING_INFO: 863 try { 864 signingInfo = BackupSigningInfoState.parseFromXml(parser); 865 } catch (XmlPullParserException e) { 866 Log.e(LOG_TAG, "Could not parse signing info for " 867 + packageName, e); 868 skipToEndOfTag(parser); 869 } 870 871 break; 872 default: 873 // ignore tag 874 Log.w(LOG_TAG, "Found unexpected tag " + parser.getName() 875 + " while restoring " + packageName); 876 skipToEndOfTag(parser); 877 } 878 879 break; 880 case END_TAG: 881 ArrayList<BackupPermissionState> permissionsToRestoreList = 882 new ArrayList<>(); 883 int numPerms = permissionsToRestore.size(); 884 for (int i = 0; i < numPerms; i++) { 885 permissionsToRestoreList.add(permissionsToRestore.valueAt(i)); 886 } 887 return new BackupPackageState( 888 packageName, 889 permissionsToRestoreList, 890 signingInfo); 891 case END_DOCUMENT: 892 throw new XmlPullParserException("Could not parse state for " 893 + packageName); 894 } 895 } 896 } 897 addNewPermissions( @onNull ArrayMap<String, BackupPermissionState> permissionsToRestore, @NonNull List<BackupPermissionState> newPermissionsToRestore)898 private static void addNewPermissions( 899 @NonNull ArrayMap<String, BackupPermissionState> permissionsToRestore, 900 @NonNull List<BackupPermissionState> newPermissionsToRestore) { 901 int numPerms = newPermissionsToRestore.size(); 902 for (int i = 0; i < numPerms; i++) { 903 BackupPermissionState newPermission = newPermissionsToRestore.get(i); 904 boolean shouldOverwrite = true; 905 if (permissionsToRestore.containsKey(newPermission.mPermissionName)) { 906 // If it already exists only overwrite if newly added state was explicitly 907 // saved while existing state was implicit by permission split. 908 shouldOverwrite = !newPermission.mIsAddedFromSplit 909 && permissionsToRestore.get(newPermission.mPermissionName) 910 .mIsAddedFromSplit; 911 } 912 if (shouldOverwrite) { 913 permissionsToRestore.put(newPermission.mPermissionName, newPermission); 914 } 915 } 916 } 917 918 /** 919 * Get the state of a package to back up. 920 * 921 * @param context A context to use 922 * @param pkgInfo The package to back up. 923 * 924 * @return The state to back up or {@code null} if no permission of the package need to be 925 * backed up. 926 */ 927 @Nullable fromAppPermissions(@onNull Context context, @NonNull PackageInfo pkgInfo)928 static BackupPackageState fromAppPermissions(@NonNull Context context, 929 @NonNull PackageInfo pkgInfo) { 930 AppPermissions appPerms = new AppPermissions(context, pkgInfo, false, null); 931 932 ArrayList<BackupPermissionState> permissionsToRestore = new ArrayList<>(); 933 List<AppPermissionGroup> groups = appPerms.getPermissionGroups(); 934 935 int numGroups = groups.size(); 936 for (int groupNum = 0; groupNum < numGroups; groupNum++) { 937 AppPermissionGroup group = groups.get(groupNum); 938 939 permissionsToRestore.addAll(BackupPermissionState.fromPermissionGroup(group)); 940 941 // Background permissions are in a subgroup that is not part of 942 // {@link AppPermission#getPermissionGroups}. Hence add it explicitly here. 943 if (group.getBackgroundPermissions() != null) { 944 permissionsToRestore.addAll(BackupPermissionState.fromPermissionGroup( 945 group.getBackgroundPermissions())); 946 } 947 } 948 949 if (permissionsToRestore.size() == 0) { 950 return null; 951 } 952 953 BackupSigningInfoState signingInfoState = null; 954 955 if (pkgInfo.signingInfo != null) { 956 signingInfoState = BackupSigningInfoState.fromSigningInfo(pkgInfo.signingInfo); 957 } 958 959 return new BackupPackageState( 960 pkgInfo.packageName, permissionsToRestore, signingInfoState); 961 } 962 963 /** 964 * Write this state as XML. 965 * 966 * @param serializer The file to write to 967 */ writeAsXml(@onNull XmlSerializer serializer)968 void writeAsXml(@NonNull XmlSerializer serializer) throws IOException { 969 if (mPermissionsToRestore.size() == 0) { 970 return; 971 } 972 973 serializer.startTag(null, TAG_GRANT); 974 serializer.attribute(null, ATTR_PACKAGE_NAME, mPackageName); 975 976 int numPerms = mPermissionsToRestore.size(); 977 for (int i = 0; i < numPerms; i++) { 978 mPermissionsToRestore.get(i).writeAsXml(serializer); 979 } 980 981 if (mBackupSigningInfoState != null) { 982 mBackupSigningInfoState.writeAsXml(serializer); 983 } 984 985 serializer.endTag(null, TAG_GRANT); 986 } 987 988 /** 989 * Restore this package state. 990 * 991 * @param context A context to use 992 * @param pkgInfo The package to restore. 993 */ restore(@onNull Context context, @NonNull PackageInfo pkgInfo)994 void restore(@NonNull Context context, @NonNull PackageInfo pkgInfo) { 995 AppPermissions appPerms = new AppPermissions(context, pkgInfo, false, true, null); 996 997 ArraySet<String> affectedPermissions = new ArraySet<>(); 998 // Restore background permissions after foreground permissions as for pre-M apps bg 999 // granted and fg revoked cannot be expressed. 1000 int numPerms = mPermissionsToRestore.size(); 1001 for (int i = 0; i < numPerms; i++) { 1002 mPermissionsToRestore.get(i).restore(appPerms, false); 1003 affectedPermissions.add(mPermissionsToRestore.get(i).mPermissionName); 1004 } 1005 for (int i = 0; i < numPerms; i++) { 1006 mPermissionsToRestore.get(i).restore(appPerms, true); 1007 } 1008 1009 int numGroups = appPerms.getPermissionGroups().size(); 1010 for (int i = 0; i < numGroups; i++) { 1011 AppPermissionGroup group = appPerms.getPermissionGroups().get(i); 1012 1013 // Only denied groups can be user fixed 1014 if (group.areRuntimePermissionsGranted()) { 1015 group.setUserFixed(false); 1016 } 1017 1018 AppPermissionGroup bgGroup = group.getBackgroundPermissions(); 1019 if (bgGroup != null) { 1020 // Only denied groups can be user fixed 1021 if (bgGroup.areRuntimePermissionsGranted()) { 1022 bgGroup.setUserFixed(false); 1023 } 1024 } 1025 } 1026 1027 appPerms.persistChanges(true, affectedPermissions); 1028 } 1029 } 1030 1031 /** 1032 * Returns whether the signing certificates of the restored app and backed up app are 1033 * compatible for the restored app to be granted the backed up app's permissions. 1034 * 1035 * <p>This returns true when any one of the following is true: 1036 * 1037 * <ul> 1038 * <li> the backed up app has multiple signing certificates and the restored app 1039 * has identical multiple signing certificates 1040 * <li> the backed up app has a single signing certificate and it is the current 1041 * single signing certificate of the restored app 1042 * <li> the backed up app has a single signing certificate and it is present in the 1043 * signing certificate history of the restored app 1044 * <li> the backed up app has a single signing certificate and signing certificate 1045 * history, and the signing certificate of the restored app is present in that history 1046 * </ul>* 1047 */ hasCompatibleSignaturesForRestore(@onNull SigningInfo restoredSigningInfo, @NonNull BackupSigningInfoState backupSigningInfoState)1048 private boolean hasCompatibleSignaturesForRestore(@NonNull SigningInfo restoredSigningInfo, 1049 @NonNull BackupSigningInfoState backupSigningInfoState) { 1050 Set<byte[]> backupCertDigests = backupSigningInfoState.mCurrentCertDigests; 1051 Set<byte[]> backupPastCertDigests = backupSigningInfoState.mPastCertDigests; 1052 Signature[] restoredSignatures = restoredSigningInfo.getApkContentsSigners(); 1053 1054 // Check that both apps have the same number of signing certificates. This will be a 1055 // required check for both the single and multiple certificate cases. 1056 if (backupCertDigests.size() != restoredSignatures.length) { 1057 return false; 1058 } 1059 1060 Set<byte[]> restoredCertDigests = new HashSet<>(); 1061 for (Signature signature: restoredSignatures) { 1062 restoredCertDigests.add(computeSha256DigestBytes(signature.toByteArray())); 1063 } 1064 1065 // If the backed up app has multiple signing certificates, the restored app should be 1066 // signed by that exact set of multiple signing certificates. 1067 if (backupCertDigests.size() > 1) { 1068 // Check that the restored certificates are a subset of the backed up certificates. 1069 if (!CollectionUtils.containsSubset(backupCertDigests, restoredCertDigests)) { 1070 return false; 1071 } 1072 // Check that the backed up certificates are a subset of the restored certificates. 1073 if (!CollectionUtils.containsSubset(restoredCertDigests, backupCertDigests)) { 1074 return false; 1075 } 1076 return true; 1077 } 1078 1079 // If both apps have a single signing certificate, we check if they are equal or if one 1080 // app's certificate is in the signing certificate history of the other. 1081 byte[] backupCertDigest = backupCertDigests.iterator().next(); 1082 byte[] restoredPastCertDigest = restoredCertDigests.iterator().next(); 1083 1084 // Check if the backed up app and restored app have the same signing certificate. 1085 if (Arrays.equals(backupCertDigest, restoredPastCertDigest)) { 1086 return true; 1087 } 1088 1089 // Check if the restored app's certificate is in the backed up app's signing certificate 1090 // history. 1091 if (CollectionUtils.contains(backupPastCertDigests, restoredPastCertDigest)) { 1092 return true; 1093 } 1094 1095 // Check if the backed up app's certificate is in the restored app's signing certificate 1096 // history. 1097 if (restoredSigningInfo.hasPastSigningCertificates()) { 1098 // The last element in the pastSigningCertificates array is the current signer; 1099 // since that was verified above, just check all the signers in the lineage. 1100 for (int i = 0; i < restoredSigningInfo.getSigningCertificateHistory().length - 1; 1101 i++) { 1102 restoredPastCertDigest = computeSha256DigestBytes( 1103 restoredSigningInfo.getSigningCertificateHistory()[i].toByteArray()); 1104 if (Arrays.equals(backupCertDigest, restoredPastCertDigest)) { 1105 return true; 1106 } 1107 } 1108 } 1109 return false; 1110 } 1111 1112 /** Computes the SHA256 digest of the provided {@code byte} array. */ 1113 @Nullable computeSha256DigestBytes(@onNull byte[] data)1114 private static byte[] computeSha256DigestBytes(@NonNull byte[] data) { 1115 MessageDigest messageDigest; 1116 try { 1117 messageDigest = MessageDigest.getInstance("SHA256"); 1118 } catch (NoSuchAlgorithmException e) { 1119 return null; 1120 } 1121 1122 messageDigest.update(data); 1123 1124 return messageDigest.digest(); 1125 } 1126 } 1127