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