• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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