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