1 /* 2 * Copyright (C) 2021 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.proxy; 18 19 import static android.os.PowerWhitelistManager.REASON_DOMAIN_VERIFICATION_V1; 20 import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; 21 22 import android.Manifest; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.app.BroadcastOptions; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.IntentFilter; 30 import android.content.pm.PackageManager; 31 import android.content.pm.verify.domain.DomainVerificationInfo; 32 import android.content.pm.verify.domain.DomainVerificationManager; 33 import android.content.pm.verify.domain.DomainVerificationState; 34 import android.os.Process; 35 import android.os.UserHandle; 36 import android.util.ArrayMap; 37 import android.util.ArraySet; 38 import android.util.Pair; 39 import android.util.Slog; 40 41 import com.android.internal.annotations.GuardedBy; 42 import com.android.server.pm.parsing.pkg.AndroidPackage; 43 import com.android.server.pm.verify.domain.DomainVerificationCollector; 44 import com.android.server.pm.verify.domain.DomainVerificationDebug; 45 import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; 46 import com.android.server.pm.verify.domain.DomainVerificationMessageCodes; 47 48 import java.util.Collections; 49 import java.util.List; 50 import java.util.Map; 51 import java.util.Objects; 52 import java.util.Set; 53 import java.util.UUID; 54 55 public class DomainVerificationProxyV1 implements DomainVerificationProxy { 56 57 private static final String TAG = "DomainVerificationProxyV1"; 58 59 private static final boolean DEBUG_BROADCASTS = DomainVerificationDebug.DEBUG_BROADCASTS; 60 61 @NonNull 62 private final Context mContext; 63 64 @NonNull 65 private final Connection mConnection; 66 67 @NonNull 68 private final ComponentName mVerifierComponent; 69 70 @NonNull 71 private final DomainVerificationManagerInternal mManager; 72 73 @NonNull 74 private final DomainVerificationCollector mCollector; 75 76 @NonNull 77 private final Object mLock = new Object(); 78 79 @NonNull 80 @GuardedBy("mLock") 81 private final ArrayMap<Integer, Pair<UUID, String>> mRequests = new ArrayMap<>(); 82 83 @GuardedBy("mLock") 84 private int mVerificationToken = 0; 85 DomainVerificationProxyV1(@onNull Context context, @NonNull DomainVerificationManagerInternal manager, @NonNull DomainVerificationCollector collector, @NonNull Connection connection, @NonNull ComponentName verifierComponent)86 public DomainVerificationProxyV1(@NonNull Context context, 87 @NonNull DomainVerificationManagerInternal manager, 88 @NonNull DomainVerificationCollector collector, @NonNull Connection connection, 89 @NonNull ComponentName verifierComponent) { 90 mContext = context; 91 mConnection = connection; 92 mVerifierComponent = verifierComponent; 93 mManager = manager; 94 mCollector = collector; 95 } 96 queueLegacyVerifyResult(@onNull Context context, @NonNull DomainVerificationProxyV1.Connection connection, int verificationId, int verificationCode, @Nullable List<String> failedDomains, int callingUid)97 public static void queueLegacyVerifyResult(@NonNull Context context, 98 @NonNull DomainVerificationProxyV1.Connection connection, int verificationId, 99 int verificationCode, @Nullable List<String> failedDomains, int callingUid) { 100 context.enforceCallingOrSelfPermission( 101 Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT, 102 "Only the intent filter verification agent can verify applications"); 103 104 connection.schedule(DomainVerificationMessageCodes.LEGACY_ON_INTENT_FILTER_VERIFIED, 105 new Response(callingUid, verificationId, verificationCode, failedDomains)); 106 } 107 108 @Override sendBroadcastForPackages(@onNull Set<String> packageNames)109 public void sendBroadcastForPackages(@NonNull Set<String> packageNames) { 110 synchronized (mLock) { 111 int size = mRequests.size(); 112 for (int index = size - 1; index >= 0; index--) { 113 Pair<UUID, String> pair = mRequests.valueAt(index); 114 if (packageNames.contains(pair.second)) { 115 mRequests.removeAt(index); 116 } 117 } 118 } 119 mConnection.schedule(DomainVerificationMessageCodes.LEGACY_SEND_REQUEST, packageNames); 120 } 121 122 @SuppressWarnings("deprecation") 123 @Override runMessage(int messageCode, Object object)124 public boolean runMessage(int messageCode, Object object) { 125 switch (messageCode) { 126 case DomainVerificationMessageCodes.LEGACY_SEND_REQUEST: 127 @SuppressWarnings("unchecked") Set<String> packageNames = (Set<String>) object; 128 if (DEBUG_BROADCASTS) { 129 Slog.d(TAG, "Requesting domain verification for " + packageNames); 130 } 131 132 ArrayMap<Integer, Pair<UUID, String>> newRequests = new ArrayMap<>( 133 packageNames.size()); 134 synchronized (mLock) { 135 for (String packageName : packageNames) { 136 UUID domainSetId = mManager.getDomainVerificationInfoId(packageName); 137 if (domainSetId == null) { 138 continue; 139 } 140 141 newRequests.put(mVerificationToken++, 142 Pair.create(domainSetId, packageName)); 143 } 144 mRequests.putAll(newRequests); 145 } 146 147 sendBroadcasts(newRequests); 148 return true; 149 case DomainVerificationMessageCodes.LEGACY_ON_INTENT_FILTER_VERIFIED: 150 Response response = (Response) object; 151 152 Pair<UUID, String> pair = mRequests.get(response.verificationId); 153 if (pair == null) { 154 return true; 155 } 156 157 UUID domainSetId = pair.first; 158 String packageName = pair.second; 159 DomainVerificationInfo info; 160 try { 161 info = mManager.getDomainVerificationInfo(packageName); 162 } catch (PackageManager.NameNotFoundException ignored) { 163 return true; 164 } 165 166 if (info == null) { 167 return true; 168 } 169 170 if (!Objects.equals(domainSetId, info.getIdentifier())) { 171 return true; 172 } 173 174 AndroidPackage pkg = mConnection.getPackage(packageName); 175 if (pkg == null) { 176 return true; 177 } 178 179 ArraySet<String> failedDomains = new ArraySet<>(response.failedDomains); 180 Map<String, Integer> hostToStateMap = info.getHostToStateMap(); 181 Set<String> hostKeySet = hostToStateMap.keySet(); 182 ArraySet<String> successfulDomains = new ArraySet<>(hostKeySet); 183 successfulDomains.removeAll(failedDomains); 184 185 // v1 doesn't handle wildcard domains, so check them here for the verifier 186 int size = successfulDomains.size(); 187 for (int index = size - 1; index >= 0; index--) { 188 String domain = successfulDomains.valueAt(index); 189 if (domain.startsWith("*.")) { 190 String nonWildcardDomain = domain.substring(2); 191 if (failedDomains.contains(nonWildcardDomain)) { 192 failedDomains.add(domain); 193 successfulDomains.removeAt(index); 194 195 // It's possible to declare a wildcard without declaring its 196 // non-wildcard equivalent, so if it wasn't originally declared, 197 // remove the transformed domain from the failed set. Otherwise the 198 // manager will not accept the failed set as it contains an undeclared 199 // domain. 200 if (!hostKeySet.contains(nonWildcardDomain)) { 201 failedDomains.remove(nonWildcardDomain); 202 } 203 } 204 } 205 } 206 207 int callingUid = response.callingUid; 208 if (!successfulDomains.isEmpty()) { 209 try { 210 if (mManager.setDomainVerificationStatusInternal(callingUid, domainSetId, 211 successfulDomains, DomainVerificationState.STATE_SUCCESS) 212 != DomainVerificationManager.STATUS_OK) { 213 Slog.e(TAG, "Failure reporting successful domains for " + packageName); 214 } 215 } catch (Exception e) { 216 Slog.e(TAG, "Failure reporting successful domains for " + packageName, e); 217 } 218 } 219 220 if (!failedDomains.isEmpty()) { 221 try { 222 if (mManager.setDomainVerificationStatusInternal(callingUid, domainSetId, 223 failedDomains, DomainVerificationState.STATE_LEGACY_FAILURE) 224 != DomainVerificationManager.STATUS_OK) { 225 Slog.e(TAG, "Failure reporting failed domains for " + packageName); 226 } 227 } catch (Exception e) { 228 Slog.e(TAG, "Failure reporting failed domains for " + packageName, e); 229 } 230 } 231 232 return true; 233 default: 234 return false; 235 } 236 } 237 238 @Override isCallerVerifier(int callingUid)239 public boolean isCallerVerifier(int callingUid) { 240 return mConnection.isCallerPackage(callingUid, mVerifierComponent.getPackageName()); 241 } 242 243 @SuppressWarnings("deprecation") sendBroadcasts(@onNull ArrayMap<Integer, Pair<UUID, String>> verifications)244 private void sendBroadcasts(@NonNull ArrayMap<Integer, Pair<UUID, String>> verifications) { 245 final long allowListTimeout = mConnection.getPowerSaveTempWhitelistAppDuration(); 246 mConnection.getDeviceIdleInternal().addPowerSaveTempWhitelistApp(Process.myUid(), 247 mVerifierComponent.getPackageName(), allowListTimeout, 248 UserHandle.USER_SYSTEM, true, REASON_DOMAIN_VERIFICATION_V1, 249 "domain verification agent"); 250 251 int size = verifications.size(); 252 for (int index = 0; index < size; index++) { 253 int verificationId = verifications.keyAt(index); 254 String packageName = verifications.valueAt(index).second; 255 AndroidPackage pkg = mConnection.getPackage(packageName); 256 257 String hostsString = buildHostsString(pkg); 258 259 Intent intent = new Intent(Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION) 260 .setComponent(mVerifierComponent) 261 .putExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_ID, 262 verificationId) 263 .putExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME, 264 IntentFilter.SCHEME_HTTPS) 265 .putExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_HOSTS, 266 hostsString) 267 .putExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME, 268 packageName) 269 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 270 271 final BroadcastOptions options = BroadcastOptions.makeBasic(); 272 options.setTemporaryAppAllowlist(allowListTimeout, 273 TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, 274 REASON_DOMAIN_VERIFICATION_V1, ""); 275 mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM, null, options.toBundle()); 276 } 277 } 278 279 @NonNull buildHostsString(@onNull AndroidPackage pkg)280 private String buildHostsString(@NonNull AndroidPackage pkg) { 281 // The collector itself handles the v1 vs v2 behavior, which is based on targetSdkVersion, 282 // not the version of the verification agent on device. 283 ArraySet<String> domains = mCollector.collectValidAutoVerifyDomains(pkg); 284 285 // v1 doesn't handle wildcard domains, so transform them here to the root 286 StringBuilder builder = new StringBuilder(); 287 int size = domains.size(); 288 for (int index = 0; index < size; index++) { 289 if (index > 0) { 290 builder.append(" "); 291 } 292 String domain = domains.valueAt(index); 293 if (domain.startsWith("*.")) { 294 domain = domain.substring(2); 295 } 296 builder.append(domain); 297 } 298 return builder.toString(); 299 } 300 301 @NonNull 302 @Override getComponentName()303 public ComponentName getComponentName() { 304 return mVerifierComponent; 305 } 306 307 private static class Response { 308 public final int callingUid; 309 public final int verificationId; 310 public final int verificationCode; 311 @NonNull 312 public final List<String> failedDomains; 313 Response(int callingUid, int verificationId, int verificationCode, @Nullable List<String> failedDomains)314 private Response(int callingUid, int verificationId, int verificationCode, 315 @Nullable List<String> failedDomains) { 316 this.callingUid = callingUid; 317 this.verificationId = verificationId; 318 this.verificationCode = verificationCode; 319 this.failedDomains = failedDomains == null ? Collections.emptyList() : failedDomains; 320 } 321 } 322 323 public interface Connection extends BaseConnection { 324 325 @Nullable getPackage(@onNull String packageName)326 AndroidPackage getPackage(@NonNull String packageName); 327 } 328 } 329