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