• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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