1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.pm.verify.domain;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.UserIdInt;
22 import android.content.UriRelativeFilter;
23 import android.content.UriRelativeFilterGroup;
24 import android.content.pm.Signature;
25 import android.content.pm.verify.domain.DomainVerificationState;
26 import android.os.UserHandle;
27 import android.text.TextUtils;
28 import android.util.ArrayMap;
29 import android.util.ArraySet;
30 import android.util.PackageUtils;
31 import android.util.SparseArray;
32 
33 import com.android.modules.utils.TypedXmlPullParser;
34 import com.android.modules.utils.TypedXmlSerializer;
35 import com.android.server.pm.SettingsXml;
36 import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState;
37 import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
38 import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
39 
40 import org.xmlpull.v1.XmlPullParserException;
41 
42 import java.io.IOException;
43 import java.util.ArrayList;
44 import java.util.Collection;
45 import java.util.Iterator;
46 import java.util.List;
47 import java.util.UUID;
48 import java.util.function.Function;
49 
50 public class DomainVerificationPersistence {
51 
52     private static final String TAG = "DomainVerificationPersistence";
53 
54     public static final String TAG_DOMAIN_VERIFICATIONS = "domain-verifications";
55     public static final String TAG_ACTIVE = "active";
56     public static final String TAG_RESTORED = "restored";
57 
58     public static final String TAG_PACKAGE_STATE = "package-state";
59     private static final String ATTR_PACKAGE_NAME = "packageName";
60     private static final String ATTR_ID = "id";
61     private static final String ATTR_HAS_AUTO_VERIFY_DOMAINS = "hasAutoVerifyDomains";
62     private static final String ATTR_SIGNATURE = "signature";
63     private static final String TAG_USER_STATES = "user-states";
64 
65     public static final String TAG_USER_STATE = "user-state";
66     public static final String ATTR_USER_ID = "userId";
67     public static final String ATTR_ALLOW_LINK_HANDLING = "allowLinkHandling";
68     public static final String TAG_ENABLED_HOSTS = "enabled-hosts";
69     public static final String TAG_HOST = "host";
70 
71     private static final String TAG_STATE = "state";
72     public static final String TAG_DOMAIN = "domain";
73     public static final String ATTR_NAME = "name";
74     public static final String ATTR_STATE = "state";
75     public static final String TAG_URI_RELATIVE_FILTER_GROUPS = "uri-relative-filter-groups";
76     public static final String TAG_URI_RELATIVE_FILTER_GROUP = "uri-relative-filter-group";
77     public static final String ATTR_ACTION = "action";
78     public static final String TAG_URI_RELATIVE_FILTER = "uri-relative-filter";
79     public static final String ATTR_URI_PART = "uri-part";
80     public static final String ATTR_PATTERN_TYPE = "pattern-type";
81     public static final String ATTR_FILTER = "filter";
82 
83     /**
84      * @param pkgNameToSignature Converts package name to a string representation of its signature.
85      *                           Usually this is the SHA-256 hash from
86      *                           {@link PackageUtils#computeSignaturesSha256Digest(Signature[])},
87      *                           but can be an arbitrary string for testing purposes. Pass non-null
88      *                           to write out signatures, or null to ignore.
89      */
writeToXml(@onNull TypedXmlSerializer xmlSerializer, @NonNull DomainVerificationStateMap<DomainVerificationPkgState> attached, @NonNull ArrayMap<String, DomainVerificationPkgState> pending, @NonNull ArrayMap<String, DomainVerificationPkgState> restored, @UserIdInt int userId, @Nullable Function<String, String> pkgNameToSignature)90     public static void writeToXml(@NonNull TypedXmlSerializer xmlSerializer,
91             @NonNull DomainVerificationStateMap<DomainVerificationPkgState> attached,
92             @NonNull ArrayMap<String, DomainVerificationPkgState> pending,
93             @NonNull ArrayMap<String, DomainVerificationPkgState> restored,
94             @UserIdInt int userId, @Nullable Function<String, String> pkgNameToSignature)
95             throws IOException {
96         try (SettingsXml.Serializer serializer = SettingsXml.serializer(xmlSerializer)) {
97             try (SettingsXml.WriteSection ignored = serializer.startSection(
98                     TAG_DOMAIN_VERIFICATIONS)) {
99                 // Both attached and pending states are written to the active set, since both
100                 // should be restored when the device reboots or runs a backup. They're merged into
101                 // the same list because at read time the distinction isn't relevant. The pending
102                 // list should generally be empty at this point anyways.
103                 ArraySet<DomainVerificationPkgState> active = new ArraySet<>();
104 
105                 int attachedSize = attached.size();
106                 for (int attachedIndex = 0; attachedIndex < attachedSize; attachedIndex++) {
107                     active.add(attached.valueAt(attachedIndex));
108                 }
109 
110                 int pendingSize = pending.size();
111                 for (int pendingIndex = 0; pendingIndex < pendingSize; pendingIndex++) {
112                     active.add(pending.valueAt(pendingIndex));
113                 }
114 
115                 try (SettingsXml.WriteSection activeSection = serializer.startSection(TAG_ACTIVE)) {
116                     writePackageStates(activeSection, active, userId, pkgNameToSignature);
117                 }
118 
119                 try (SettingsXml.WriteSection restoredSection = serializer.startSection(
120                         TAG_RESTORED)) {
121                     writePackageStates(restoredSection, restored.values(), userId,
122                             pkgNameToSignature);
123                 }
124             }
125         }
126     }
127 
writePackageStates(@onNull SettingsXml.WriteSection section, @NonNull Collection<DomainVerificationPkgState> states, int userId, @Nullable Function<String, String> pkgNameToSignature)128     private static void writePackageStates(@NonNull SettingsXml.WriteSection section,
129             @NonNull Collection<DomainVerificationPkgState> states, int userId,
130             @Nullable Function<String, String> pkgNameToSignature) throws IOException {
131         if (states.isEmpty()) {
132             return;
133         }
134 
135         for (DomainVerificationPkgState state : states) {
136             writePkgStateToXml(section, state, userId, pkgNameToSignature);
137         }
138     }
139 
140     @NonNull
readFromXml(@onNull TypedXmlPullParser parentParser)141     public static ReadResult readFromXml(@NonNull TypedXmlPullParser parentParser)
142             throws IOException, XmlPullParserException {
143         ArrayMap<String, DomainVerificationPkgState> active = new ArrayMap<>();
144         ArrayMap<String, DomainVerificationPkgState> restored = new ArrayMap<>();
145 
146         SettingsXml.ChildSection child = SettingsXml.parser(parentParser).children();
147         while (child.moveToNext()) {
148             switch (child.getName()) {
149                 case TAG_ACTIVE:
150                     readPackageStates(child, active);
151                     break;
152                 case TAG_RESTORED:
153                     readPackageStates(child, restored);
154                     break;
155             }
156         }
157 
158         return new ReadResult(active, restored);
159     }
160 
readPackageStates(@onNull SettingsXml.ReadSection section, @NonNull ArrayMap<String, DomainVerificationPkgState> map)161     private static void readPackageStates(@NonNull SettingsXml.ReadSection section,
162             @NonNull ArrayMap<String, DomainVerificationPkgState> map) {
163         SettingsXml.ChildSection child = section.children();
164         while (child.moveToNext(TAG_PACKAGE_STATE)) {
165             DomainVerificationPkgState pkgState = createPkgStateFromXml(child);
166             if (pkgState != null) {
167                 // State is unique by package name
168                 map.put(pkgState.getPackageName(), pkgState);
169             }
170         }
171     }
172 
173     /**
174      * Reads a package state from XML. Assumes the starting {@link #TAG_PACKAGE_STATE} has already
175      * been entered.
176      */
177     @Nullable
createPkgStateFromXml( @onNull SettingsXml.ReadSection section)178     private static DomainVerificationPkgState createPkgStateFromXml(
179             @NonNull SettingsXml.ReadSection section) {
180         String packageName = section.getString(ATTR_PACKAGE_NAME);
181         String idString = section.getString(ATTR_ID);
182         boolean hasAutoVerifyDomains = section.getBoolean(ATTR_HAS_AUTO_VERIFY_DOMAINS);
183         String signature = section.getString(ATTR_SIGNATURE);
184         if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(idString)) {
185             return null;
186         }
187         UUID id = UUID.fromString(idString);
188 
189         final ArrayMap<String, Integer> stateMap = new ArrayMap<>();
190         final SparseArray<DomainVerificationInternalUserState> userStates = new SparseArray<>();
191         final ArrayMap<String, List<UriRelativeFilterGroup>> groupMap = new ArrayMap<>();
192 
193         SettingsXml.ChildSection child = section.children();
194         while (child.moveToNext()) {
195             switch (child.getName()) {
196                 case TAG_STATE:
197                     readDomainStates(child, stateMap);
198                     break;
199                 case TAG_USER_STATES:
200                     readUserStates(child, userStates);
201                     break;
202                 case TAG_URI_RELATIVE_FILTER_GROUPS:
203                     readUriRelativeFilterGroups(child, groupMap);
204                     break;
205             }
206         }
207 
208         return new DomainVerificationPkgState(packageName, id, hasAutoVerifyDomains, stateMap,
209                 userStates, signature, groupMap);
210     }
211 
readUriRelativeFilterGroups(@onNull SettingsXml.ReadSection section, @NonNull ArrayMap<String, List<UriRelativeFilterGroup>> groupMap)212     private static void readUriRelativeFilterGroups(@NonNull SettingsXml.ReadSection section,
213             @NonNull ArrayMap<String, List<UriRelativeFilterGroup>> groupMap) {
214         SettingsXml.ChildSection child = section.children();
215         while (child.moveToNext(TAG_DOMAIN)) {
216             String domain = child.getString(ATTR_NAME);
217             groupMap.put(domain, createUriRelativeFilterGroupsFromXml(child));
218         }
219     }
220 
createUriRelativeFilterGroupsFromXml( @onNull SettingsXml.ReadSection section)221     private static ArrayList<UriRelativeFilterGroup> createUriRelativeFilterGroupsFromXml(
222             @NonNull SettingsXml.ReadSection section) {
223         SettingsXml.ChildSection child = section.children();
224         ArrayList<UriRelativeFilterGroup> groups = new ArrayList<>();
225         while (child.moveToNext(TAG_URI_RELATIVE_FILTER_GROUP)) {
226             UriRelativeFilterGroup group = new UriRelativeFilterGroup(section.getInt(ATTR_ACTION));
227             readUriRelativeFiltersFromXml(child, group);
228             groups.add(group);
229         }
230         return groups;
231     }
232 
readUriRelativeFiltersFromXml( @onNull SettingsXml.ReadSection section, UriRelativeFilterGroup group)233     private static void readUriRelativeFiltersFromXml(
234             @NonNull SettingsXml.ReadSection section, UriRelativeFilterGroup group) {
235         SettingsXml.ChildSection child = section.children();
236         while (child.moveToNext(TAG_URI_RELATIVE_FILTER)) {
237             String filter = child.getString(ATTR_FILTER);
238             if (filter != null) {
239                 group.addUriRelativeFilter(new UriRelativeFilter(child.getInt(ATTR_URI_PART),
240                         child.getInt(ATTR_PATTERN_TYPE), filter));
241             }
242         }
243     }
244 
readUserStates(@onNull SettingsXml.ReadSection section, @NonNull SparseArray<DomainVerificationInternalUserState> userStates)245     private static void readUserStates(@NonNull SettingsXml.ReadSection section,
246             @NonNull SparseArray<DomainVerificationInternalUserState> userStates) {
247         SettingsXml.ChildSection child = section.children();
248         while (child.moveToNext(TAG_USER_STATE)) {
249             DomainVerificationInternalUserState userState = createUserStateFromXml(child);
250             if (userState != null) {
251                 userStates.put(userState.getUserId(), userState);
252             }
253         }
254     }
255 
readDomainStates(@onNull SettingsXml.ReadSection stateSection, @NonNull ArrayMap<String, Integer> stateMap)256     private static void readDomainStates(@NonNull SettingsXml.ReadSection stateSection,
257             @NonNull ArrayMap<String, Integer> stateMap) {
258         SettingsXml.ChildSection child = stateSection.children();
259         while (child.moveToNext(TAG_DOMAIN)) {
260             String name = child.getString(ATTR_NAME);
261             int state = child.getInt(ATTR_STATE, DomainVerificationState.STATE_NO_RESPONSE);
262             stateMap.put(name, state);
263         }
264     }
265 
writePkgStateToXml(@onNull SettingsXml.WriteSection parentSection, @NonNull DomainVerificationPkgState pkgState, @UserIdInt int userId, @Nullable Function<String, String> pkgNameToSignature)266     private static void writePkgStateToXml(@NonNull SettingsXml.WriteSection parentSection,
267             @NonNull DomainVerificationPkgState pkgState, @UserIdInt int userId,
268             @Nullable Function<String, String> pkgNameToSignature) throws IOException {
269         String packageName = pkgState.getPackageName();
270         String signature = pkgNameToSignature == null
271                 ? null : pkgNameToSignature.apply(packageName);
272         if (signature == null) {
273             // If a package isn't available to get its signature, fallback to the previously stored
274             // result, which can occur if the package has been marked for restore but hasn't
275             // been installed on the new device yet.
276             signature = pkgState.getBackupSignatureHash();
277         }
278 
279         try (SettingsXml.WriteSection ignored =
280                      parentSection.startSection(TAG_PACKAGE_STATE)
281                              .attribute(ATTR_PACKAGE_NAME, packageName)
282                              .attribute(ATTR_ID, pkgState.getId().toString())
283                              .attribute(ATTR_HAS_AUTO_VERIFY_DOMAINS,
284                                      pkgState.isHasAutoVerifyDomains())
285                              .attribute(ATTR_SIGNATURE, signature)) {
286             writeStateMap(parentSection, pkgState.getStateMap());
287             writeUserStates(parentSection, userId, pkgState.getUserStates());
288             writeUriRelativeFilterGroupMap(parentSection, pkgState.getUriRelativeFilterGroupMap());
289         }
290     }
291 
writeUserStates(@onNull SettingsXml.WriteSection parentSection, @UserIdInt int userId, @NonNull SparseArray<DomainVerificationInternalUserState> states)292     private static void writeUserStates(@NonNull SettingsXml.WriteSection parentSection,
293             @UserIdInt int userId,
294             @NonNull SparseArray<DomainVerificationInternalUserState> states) throws IOException {
295         int size = states.size();
296         if (size == 0) {
297             return;
298         }
299 
300         try (SettingsXml.WriteSection section = parentSection.startSection(TAG_USER_STATES)) {
301             if (userId == UserHandle.USER_ALL) {
302                 for (int index = 0; index < size; index++) {
303                     writeUserStateToXml(section, states.valueAt(index));
304                 }
305             } else {
306                 DomainVerificationInternalUserState userState = states.get(userId);
307                 if (userState != null) {
308                     writeUserStateToXml(section, userState);
309                 }
310             }
311         }
312     }
313 
writeStateMap(@onNull SettingsXml.WriteSection parentSection, @NonNull ArrayMap<String, Integer> stateMap)314     private static void writeStateMap(@NonNull SettingsXml.WriteSection parentSection,
315             @NonNull ArrayMap<String, Integer> stateMap) throws IOException {
316         if (stateMap.isEmpty()) {
317             return;
318         }
319 
320         try (SettingsXml.WriteSection stateSection = parentSection.startSection(TAG_STATE)) {
321             int size = stateMap.size();
322             for (int index = 0; index < size; index++) {
323                 stateSection.startSection(TAG_DOMAIN)
324                         .attribute(ATTR_NAME, stateMap.keyAt(index))
325                         .attribute(ATTR_STATE, stateMap.valueAt(index))
326                         .finish();
327             }
328         }
329     }
330 
331     /**
332      * Reads a user state from XML. Assumes the starting {@link #TAG_USER_STATE} has already been
333      * entered.
334      */
335     @Nullable
createUserStateFromXml( @onNull SettingsXml.ReadSection section)336     private static DomainVerificationInternalUserState createUserStateFromXml(
337             @NonNull SettingsXml.ReadSection section) {
338         int userId = section.getInt(ATTR_USER_ID);
339         if (userId == -1) {
340             return null;
341         }
342 
343         boolean allowLinkHandling = section.getBoolean(ATTR_ALLOW_LINK_HANDLING, false);
344         ArraySet<String> enabledHosts = new ArraySet<>();
345 
346         SettingsXml.ChildSection child = section.children();
347         while (child.moveToNext(TAG_ENABLED_HOSTS)) {
348             readEnabledHosts(child, enabledHosts);
349         }
350 
351         return new DomainVerificationInternalUserState(userId, enabledHosts, allowLinkHandling);
352     }
353 
readEnabledHosts(@onNull SettingsXml.ReadSection section, @NonNull ArraySet<String> enabledHosts)354     private static void readEnabledHosts(@NonNull SettingsXml.ReadSection section,
355             @NonNull ArraySet<String> enabledHosts) {
356         SettingsXml.ChildSection child = section.children();
357         while (child.moveToNext(TAG_HOST)) {
358             String hostName = child.getString(ATTR_NAME);
359             if (!TextUtils.isEmpty(hostName)) {
360                 enabledHosts.add(hostName);
361             }
362         }
363     }
364 
writeUserStateToXml(@onNull SettingsXml.WriteSection parentSection, @NonNull DomainVerificationInternalUserState userState)365     private static void writeUserStateToXml(@NonNull SettingsXml.WriteSection parentSection,
366             @NonNull DomainVerificationInternalUserState userState) throws IOException {
367         try (SettingsXml.WriteSection section =
368                      parentSection.startSection(TAG_USER_STATE)
369                              .attribute(ATTR_USER_ID, userState.getUserId())
370                              .attribute(ATTR_ALLOW_LINK_HANDLING,
371                                      userState.isLinkHandlingAllowed())) {
372             ArraySet<String> enabledHosts = userState.getEnabledHosts();
373             if (!enabledHosts.isEmpty()) {
374                 try (SettingsXml.WriteSection enabledHostsSection =
375                              section.startSection(TAG_ENABLED_HOSTS)) {
376                     int size = enabledHosts.size();
377                     for (int index = 0; index < size; index++) {
378                         enabledHostsSection.startSection(TAG_HOST)
379                                 .attribute(ATTR_NAME, enabledHosts.valueAt(index))
380                                 .finish();
381                     }
382                 }
383             }
384         }
385     }
386 
writeUriRelativeFilterGroupMap( @onNull SettingsXml.WriteSection parentSection, @NonNull ArrayMap<String, List<UriRelativeFilterGroup>> groupMap)387     private static void writeUriRelativeFilterGroupMap(
388             @NonNull SettingsXml.WriteSection parentSection,
389             @NonNull ArrayMap<String, List<UriRelativeFilterGroup>> groupMap) throws IOException {
390         if (groupMap.isEmpty()) {
391             return;
392         }
393         try (SettingsXml.WriteSection section =
394                      parentSection.startSection(TAG_URI_RELATIVE_FILTER_GROUPS)) {
395             for (int i = 0; i < groupMap.size(); i++) {
396                 writeUriRelativeFilterGroups(section, groupMap.keyAt(i), groupMap.valueAt(i));
397             }
398         }
399     }
400 
writeUriRelativeFilterGroups( @onNull SettingsXml.WriteSection parentSection, @NonNull String domain, @NonNull List<UriRelativeFilterGroup> groups)401     private static void writeUriRelativeFilterGroups(
402             @NonNull SettingsXml.WriteSection parentSection, @NonNull String domain,
403             @NonNull List<UriRelativeFilterGroup> groups) throws IOException {
404         if (groups.isEmpty()) {
405             return;
406         }
407         try (SettingsXml.WriteSection section =
408                      parentSection.startSection(TAG_DOMAIN)
409                              .attribute(ATTR_NAME, domain)) {
410             for (int i = 0; i < groups.size(); i++) {
411                 writeUriRelativeFilterGroup(section, groups.get(i));
412             }
413         }
414     }
415 
writeUriRelativeFilterGroup( @onNull SettingsXml.WriteSection parentSection, @NonNull UriRelativeFilterGroup group)416     private static void writeUriRelativeFilterGroup(
417             @NonNull SettingsXml.WriteSection parentSection,
418             @NonNull UriRelativeFilterGroup group) throws IOException {
419         try (SettingsXml.WriteSection section =
420                      parentSection.startSection(TAG_URI_RELATIVE_FILTER_GROUP)
421                              .attribute(ATTR_ACTION, group.getAction())) {
422             Iterator<UriRelativeFilter> it = group.getUriRelativeFilters().iterator();
423             while (it.hasNext()) {
424                 UriRelativeFilter filter = it.next();
425                 section.startSection(TAG_URI_RELATIVE_FILTER)
426                         .attribute(ATTR_URI_PART, filter.getUriPart())
427                         .attribute(ATTR_PATTERN_TYPE, filter.getPatternType())
428                         .attribute(ATTR_FILTER, filter.getFilter()).finish();
429             }
430         }
431     }
432 
433     public static class ReadResult {
434 
435         @NonNull
436         public final ArrayMap<String, DomainVerificationPkgState> active;
437 
438         @NonNull
439         public final ArrayMap<String, DomainVerificationPkgState> restored;
440 
ReadResult(@onNull ArrayMap<String, DomainVerificationPkgState> active, @NonNull ArrayMap<String, DomainVerificationPkgState> restored)441         public ReadResult(@NonNull ArrayMap<String, DomainVerificationPkgState> active,
442                 @NonNull ArrayMap<String, DomainVerificationPkgState> restored) {
443             this.active = active;
444             this.restored = restored;
445         }
446     }
447 }
448