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.pm.Signature;
23 import android.content.pm.verify.domain.DomainVerificationState;
24 import android.os.UserHandle;
25 import android.text.TextUtils;
26 import android.util.ArrayMap;
27 import android.util.ArraySet;
28 import android.util.PackageUtils;
29 import android.util.SparseArray;
30 
31 import com.android.modules.utils.TypedXmlPullParser;
32 import com.android.modules.utils.TypedXmlSerializer;
33 import com.android.server.pm.SettingsXml;
34 import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState;
35 import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
36 import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
37 
38 import org.xmlpull.v1.XmlPullParserException;
39 
40 import java.io.IOException;
41 import java.util.Collection;
42 import java.util.UUID;
43 import java.util.function.Function;
44 
45 public class DomainVerificationPersistence {
46 
47     private static final String TAG = "DomainVerificationPersistence";
48 
49     public static final String TAG_DOMAIN_VERIFICATIONS = "domain-verifications";
50     public static final String TAG_ACTIVE = "active";
51     public static final String TAG_RESTORED = "restored";
52 
53     public static final String TAG_PACKAGE_STATE = "package-state";
54     private static final String ATTR_PACKAGE_NAME = "packageName";
55     private static final String ATTR_ID = "id";
56     private static final String ATTR_HAS_AUTO_VERIFY_DOMAINS = "hasAutoVerifyDomains";
57     private static final String ATTR_SIGNATURE = "signature";
58     private static final String TAG_USER_STATES = "user-states";
59 
60     public static final String TAG_USER_STATE = "user-state";
61     public static final String ATTR_USER_ID = "userId";
62     public static final String ATTR_ALLOW_LINK_HANDLING = "allowLinkHandling";
63     public static final String TAG_ENABLED_HOSTS = "enabled-hosts";
64     public static final String TAG_HOST = "host";
65 
66     private static final String TAG_STATE = "state";
67     public static final String TAG_DOMAIN = "domain";
68     public static final String ATTR_NAME = "name";
69     public static final String ATTR_STATE = "state";
70 
71     /**
72      * @param pkgNameToSignature Converts package name to a string representation of its signature.
73      *                           Usually this is the SHA-256 hash from
74      *                           {@link PackageUtils#computeSignaturesSha256Digest(Signature[])},
75      *                           but can be an arbitrary string for testing purposes. Pass non-null
76      *                           to write out signatures, or null to ignore.
77      */
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)78     public static void writeToXml(@NonNull TypedXmlSerializer xmlSerializer,
79             @NonNull DomainVerificationStateMap<DomainVerificationPkgState> attached,
80             @NonNull ArrayMap<String, DomainVerificationPkgState> pending,
81             @NonNull ArrayMap<String, DomainVerificationPkgState> restored,
82             @UserIdInt int userId, @Nullable Function<String, String> pkgNameToSignature)
83             throws IOException {
84         try (SettingsXml.Serializer serializer = SettingsXml.serializer(xmlSerializer)) {
85             try (SettingsXml.WriteSection ignored = serializer.startSection(
86                     TAG_DOMAIN_VERIFICATIONS)) {
87                 // Both attached and pending states are written to the active set, since both
88                 // should be restored when the device reboots or runs a backup. They're merged into
89                 // the same list because at read time the distinction isn't relevant. The pending
90                 // list should generally be empty at this point anyways.
91                 ArraySet<DomainVerificationPkgState> active = new ArraySet<>();
92 
93                 int attachedSize = attached.size();
94                 for (int attachedIndex = 0; attachedIndex < attachedSize; attachedIndex++) {
95                     active.add(attached.valueAt(attachedIndex));
96                 }
97 
98                 int pendingSize = pending.size();
99                 for (int pendingIndex = 0; pendingIndex < pendingSize; pendingIndex++) {
100                     active.add(pending.valueAt(pendingIndex));
101                 }
102 
103                 try (SettingsXml.WriteSection activeSection = serializer.startSection(TAG_ACTIVE)) {
104                     writePackageStates(activeSection, active, userId, pkgNameToSignature);
105                 }
106 
107                 try (SettingsXml.WriteSection restoredSection = serializer.startSection(
108                         TAG_RESTORED)) {
109                     writePackageStates(restoredSection, restored.values(), userId,
110                             pkgNameToSignature);
111                 }
112             }
113         }
114     }
115 
writePackageStates(@onNull SettingsXml.WriteSection section, @NonNull Collection<DomainVerificationPkgState> states, int userId, @Nullable Function<String, String> pkgNameToSignature)116     private static void writePackageStates(@NonNull SettingsXml.WriteSection section,
117             @NonNull Collection<DomainVerificationPkgState> states, int userId,
118             @Nullable Function<String, String> pkgNameToSignature) throws IOException {
119         if (states.isEmpty()) {
120             return;
121         }
122 
123         for (DomainVerificationPkgState state : states) {
124             writePkgStateToXml(section, state, userId, pkgNameToSignature);
125         }
126     }
127 
128     @NonNull
readFromXml(@onNull TypedXmlPullParser parentParser)129     public static ReadResult readFromXml(@NonNull TypedXmlPullParser parentParser)
130             throws IOException, XmlPullParserException {
131         ArrayMap<String, DomainVerificationPkgState> active = new ArrayMap<>();
132         ArrayMap<String, DomainVerificationPkgState> restored = new ArrayMap<>();
133 
134         SettingsXml.ChildSection child = SettingsXml.parser(parentParser).children();
135         while (child.moveToNext()) {
136             switch (child.getName()) {
137                 case TAG_ACTIVE:
138                     readPackageStates(child, active);
139                     break;
140                 case TAG_RESTORED:
141                     readPackageStates(child, restored);
142                     break;
143             }
144         }
145 
146         return new ReadResult(active, restored);
147     }
148 
readPackageStates(@onNull SettingsXml.ReadSection section, @NonNull ArrayMap<String, DomainVerificationPkgState> map)149     private static void readPackageStates(@NonNull SettingsXml.ReadSection section,
150             @NonNull ArrayMap<String, DomainVerificationPkgState> map) {
151         SettingsXml.ChildSection child = section.children();
152         while (child.moveToNext(TAG_PACKAGE_STATE)) {
153             DomainVerificationPkgState pkgState = createPkgStateFromXml(child);
154             if (pkgState != null) {
155                 // State is unique by package name
156                 map.put(pkgState.getPackageName(), pkgState);
157             }
158         }
159     }
160 
161     /**
162      * Reads a package state from XML. Assumes the starting {@link #TAG_PACKAGE_STATE} has already
163      * been entered.
164      */
165     @Nullable
createPkgStateFromXml( @onNull SettingsXml.ReadSection section)166     private static DomainVerificationPkgState createPkgStateFromXml(
167             @NonNull SettingsXml.ReadSection section) {
168         String packageName = section.getString(ATTR_PACKAGE_NAME);
169         String idString = section.getString(ATTR_ID);
170         boolean hasAutoVerifyDomains = section.getBoolean(ATTR_HAS_AUTO_VERIFY_DOMAINS);
171         String signature = section.getString(ATTR_SIGNATURE);
172         if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(idString)) {
173             return null;
174         }
175         UUID id = UUID.fromString(idString);
176 
177         final ArrayMap<String, Integer> stateMap = new ArrayMap<>();
178         final SparseArray<DomainVerificationInternalUserState> userStates = new SparseArray<>();
179 
180         SettingsXml.ChildSection child = section.children();
181         while (child.moveToNext()) {
182             switch (child.getName()) {
183                 case TAG_STATE:
184                     readDomainStates(child, stateMap);
185                     break;
186                 case TAG_USER_STATES:
187                     readUserStates(child, userStates);
188                     break;
189             }
190         }
191 
192         return new DomainVerificationPkgState(packageName, id, hasAutoVerifyDomains, stateMap,
193                 userStates, signature);
194     }
195 
readUserStates(@onNull SettingsXml.ReadSection section, @NonNull SparseArray<DomainVerificationInternalUserState> userStates)196     private static void readUserStates(@NonNull SettingsXml.ReadSection section,
197             @NonNull SparseArray<DomainVerificationInternalUserState> userStates) {
198         SettingsXml.ChildSection child = section.children();
199         while (child.moveToNext(TAG_USER_STATE)) {
200             DomainVerificationInternalUserState userState = createUserStateFromXml(child);
201             if (userState != null) {
202                 userStates.put(userState.getUserId(), userState);
203             }
204         }
205     }
206 
readDomainStates(@onNull SettingsXml.ReadSection stateSection, @NonNull ArrayMap<String, Integer> stateMap)207     private static void readDomainStates(@NonNull SettingsXml.ReadSection stateSection,
208             @NonNull ArrayMap<String, Integer> stateMap) {
209         SettingsXml.ChildSection child = stateSection.children();
210         while (child.moveToNext(TAG_DOMAIN)) {
211             String name = child.getString(ATTR_NAME);
212             int state = child.getInt(ATTR_STATE, DomainVerificationState.STATE_NO_RESPONSE);
213             stateMap.put(name, state);
214         }
215     }
216 
writePkgStateToXml(@onNull SettingsXml.WriteSection parentSection, @NonNull DomainVerificationPkgState pkgState, @UserIdInt int userId, @Nullable Function<String, String> pkgNameToSignature)217     private static void writePkgStateToXml(@NonNull SettingsXml.WriteSection parentSection,
218             @NonNull DomainVerificationPkgState pkgState, @UserIdInt int userId,
219             @Nullable Function<String, String> pkgNameToSignature) throws IOException {
220         String packageName = pkgState.getPackageName();
221         String signature = pkgNameToSignature == null
222                 ? null : pkgNameToSignature.apply(packageName);
223         if (signature == null) {
224             // If a package isn't available to get its signature, fallback to the previously stored
225             // result, which can occur if the package has been marked for restore but hasn't
226             // been installed on the new device yet.
227             signature = pkgState.getBackupSignatureHash();
228         }
229 
230         try (SettingsXml.WriteSection ignored =
231                      parentSection.startSection(TAG_PACKAGE_STATE)
232                              .attribute(ATTR_PACKAGE_NAME, packageName)
233                              .attribute(ATTR_ID, pkgState.getId().toString())
234                              .attribute(ATTR_HAS_AUTO_VERIFY_DOMAINS,
235                                      pkgState.isHasAutoVerifyDomains())
236                              .attribute(ATTR_SIGNATURE, signature)) {
237             writeStateMap(parentSection, pkgState.getStateMap());
238             writeUserStates(parentSection, userId, pkgState.getUserStates());
239         }
240     }
241 
writeUserStates(@onNull SettingsXml.WriteSection parentSection, @UserIdInt int userId, @NonNull SparseArray<DomainVerificationInternalUserState> states)242     private static void writeUserStates(@NonNull SettingsXml.WriteSection parentSection,
243             @UserIdInt int userId,
244             @NonNull SparseArray<DomainVerificationInternalUserState> states) throws IOException {
245         int size = states.size();
246         if (size == 0) {
247             return;
248         }
249 
250         try (SettingsXml.WriteSection section = parentSection.startSection(TAG_USER_STATES)) {
251             if (userId == UserHandle.USER_ALL) {
252                 for (int index = 0; index < size; index++) {
253                     writeUserStateToXml(section, states.valueAt(index));
254                 }
255             } else {
256                 DomainVerificationInternalUserState userState = states.get(userId);
257                 if (userState != null) {
258                     writeUserStateToXml(section, userState);
259                 }
260             }
261         }
262     }
263 
writeStateMap(@onNull SettingsXml.WriteSection parentSection, @NonNull ArrayMap<String, Integer> stateMap)264     private static void writeStateMap(@NonNull SettingsXml.WriteSection parentSection,
265             @NonNull ArrayMap<String, Integer> stateMap) throws IOException {
266         if (stateMap.isEmpty()) {
267             return;
268         }
269 
270         try (SettingsXml.WriteSection stateSection = parentSection.startSection(TAG_STATE)) {
271             int size = stateMap.size();
272             for (int index = 0; index < size; index++) {
273                 stateSection.startSection(TAG_DOMAIN)
274                         .attribute(ATTR_NAME, stateMap.keyAt(index))
275                         .attribute(ATTR_STATE, stateMap.valueAt(index))
276                         .finish();
277             }
278         }
279     }
280 
281     /**
282      * Reads a user state from XML. Assumes the starting {@link #TAG_USER_STATE} has already been
283      * entered.
284      */
285     @Nullable
createUserStateFromXml( @onNull SettingsXml.ReadSection section)286     private static DomainVerificationInternalUserState createUserStateFromXml(
287             @NonNull SettingsXml.ReadSection section) {
288         int userId = section.getInt(ATTR_USER_ID);
289         if (userId == -1) {
290             return null;
291         }
292 
293         boolean allowLinkHandling = section.getBoolean(ATTR_ALLOW_LINK_HANDLING, false);
294         ArraySet<String> enabledHosts = new ArraySet<>();
295 
296         SettingsXml.ChildSection child = section.children();
297         while (child.moveToNext(TAG_ENABLED_HOSTS)) {
298             readEnabledHosts(child, enabledHosts);
299         }
300 
301         return new DomainVerificationInternalUserState(userId, enabledHosts, allowLinkHandling);
302     }
303 
readEnabledHosts(@onNull SettingsXml.ReadSection section, @NonNull ArraySet<String> enabledHosts)304     private static void readEnabledHosts(@NonNull SettingsXml.ReadSection section,
305             @NonNull ArraySet<String> enabledHosts) {
306         SettingsXml.ChildSection child = section.children();
307         while (child.moveToNext(TAG_HOST)) {
308             String hostName = child.getString(ATTR_NAME);
309             if (!TextUtils.isEmpty(hostName)) {
310                 enabledHosts.add(hostName);
311             }
312         }
313     }
314 
writeUserStateToXml(@onNull SettingsXml.WriteSection parentSection, @NonNull DomainVerificationInternalUserState userState)315     private static void writeUserStateToXml(@NonNull SettingsXml.WriteSection parentSection,
316             @NonNull DomainVerificationInternalUserState userState) throws IOException {
317         try (SettingsXml.WriteSection section =
318                      parentSection.startSection(TAG_USER_STATE)
319                              .attribute(ATTR_USER_ID, userState.getUserId())
320                              .attribute(ATTR_ALLOW_LINK_HANDLING,
321                                      userState.isLinkHandlingAllowed())) {
322             ArraySet<String> enabledHosts = userState.getEnabledHosts();
323             if (!enabledHosts.isEmpty()) {
324                 try (SettingsXml.WriteSection enabledHostsSection =
325                              section.startSection(TAG_ENABLED_HOSTS)) {
326                     int size = enabledHosts.size();
327                     for (int index = 0; index < size; index++) {
328                         enabledHostsSection.startSection(TAG_HOST)
329                                 .attribute(ATTR_NAME, enabledHosts.valueAt(index))
330                                 .finish();
331                     }
332                 }
333             }
334         }
335     }
336 
337     public static class ReadResult {
338 
339         @NonNull
340         public final ArrayMap<String, DomainVerificationPkgState> active;
341 
342         @NonNull
343         public final ArrayMap<String, DomainVerificationPkgState> restored;
344 
ReadResult(@onNull ArrayMap<String, DomainVerificationPkgState> active, @NonNull ArrayMap<String, DomainVerificationPkgState> restored)345         public ReadResult(@NonNull ArrayMap<String, DomainVerificationPkgState> active,
346                 @NonNull ArrayMap<String, DomainVerificationPkgState> restored) {
347             this.active = active;
348             this.restored = restored;
349         }
350     }
351 }
352