• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.wifi.hotspot2;
18 
19 import static android.app.AppOpsManager.OPSTR_CHANGE_WIFI_STATE;
20 import static android.net.wifi.WifiConfiguration.MeteredOverride;
21 import static android.net.wifi.WifiInfo.DEFAULT_MAC_ADDRESS;
22 
23 import static java.security.cert.PKIXReason.NO_TRUST_ANCHOR;
24 
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.app.AppOpsManager;
28 import android.content.Context;
29 import android.net.MacAddress;
30 import android.net.wifi.ScanResult;
31 import android.net.wifi.WifiConfiguration;
32 import android.net.wifi.WifiEnterpriseConfig;
33 import android.net.wifi.WifiManager;
34 import android.net.wifi.WifiSsid;
35 import android.net.wifi.hotspot2.IProvisioningCallback;
36 import android.net.wifi.hotspot2.OsuProvider;
37 import android.net.wifi.hotspot2.PasspointConfiguration;
38 import android.os.Handler;
39 import android.os.Looper;
40 import android.os.Process;
41 import android.text.TextUtils;
42 import android.util.Log;
43 import android.util.Pair;
44 
45 import com.android.internal.annotations.VisibleForTesting;
46 import com.android.modules.utils.build.SdkLevel;
47 import com.android.server.wifi.Clock;
48 import com.android.server.wifi.MacAddressUtil;
49 import com.android.server.wifi.NetworkUpdateResult;
50 import com.android.server.wifi.WifiCarrierInfoManager;
51 import com.android.server.wifi.WifiConfigManager;
52 import com.android.server.wifi.WifiConfigStore;
53 import com.android.server.wifi.WifiInjector;
54 import com.android.server.wifi.WifiKeyStore;
55 import com.android.server.wifi.WifiMetrics;
56 import com.android.server.wifi.WifiNative;
57 import com.android.server.wifi.hotspot2.anqp.ANQPElement;
58 import com.android.server.wifi.hotspot2.anqp.Constants;
59 import com.android.server.wifi.hotspot2.anqp.HSOsuProvidersElement;
60 import com.android.server.wifi.hotspot2.anqp.I18Name;
61 import com.android.server.wifi.hotspot2.anqp.OsuProviderInfo;
62 import com.android.server.wifi.hotspot2.anqp.VenueNameElement;
63 import com.android.server.wifi.hotspot2.anqp.VenueUrlElement;
64 import com.android.server.wifi.proto.nano.WifiMetricsProto.UserActionEvent;
65 import com.android.server.wifi.util.InformationElementUtil;
66 import com.android.server.wifi.util.WifiPermissionsUtil;
67 
68 import java.io.IOException;
69 import java.io.PrintWriter;
70 import java.net.URL;
71 import java.security.GeneralSecurityException;
72 import java.security.KeyStore;
73 import java.security.cert.CertPath;
74 import java.security.cert.CertPathValidator;
75 import java.security.cert.CertPathValidatorException;
76 import java.security.cert.CertificateFactory;
77 import java.security.cert.PKIXParameters;
78 import java.security.cert.X509Certificate;
79 import java.util.ArrayList;
80 import java.util.Arrays;
81 import java.util.Collections;
82 import java.util.HashMap;
83 import java.util.HashSet;
84 import java.util.List;
85 import java.util.Locale;
86 import java.util.Map;
87 import java.util.Set;
88 import java.util.stream.Collectors;
89 
90 /**
91  * This class provides the APIs to manage Passpoint provider configurations.
92  * It deals with the following:
93  * - Maintaining a list of configured Passpoint providers for provider matching.
94  * - Persisting the providers configurations to store when required.
95  * - matching Passpoint providers based on the scan results
96  * - Supporting WifiManager Public API calls:
97  *   > addOrUpdatePasspointConfiguration()
98  *   > removePasspointConfiguration()
99  *   > getPasspointConfigurations()
100  *
101  * The provider matching requires obtaining additional information from the AP (ANQP elements).
102  * The ANQP elements will be cached using {@link AnqpCache} to avoid unnecessary requests.
103  *
104  * NOTE: These API's are not thread safe and should only be used from the main Wifi thread.
105  */
106 public class PasspointManager {
107     private static final String TAG = "PasspointManager";
108 
109     /**
110      * Handle for the current {@link PasspointManager} instance.  This is needed to avoid
111      * circular dependency with the WifiConfigManger, it will be used for adding the
112      * legacy Passpoint configurations.
113      *
114      * This can be eliminated once we can remove the dependency for WifiConfigManager (for
115      * triggering config store write) from this class.
116      */
117     private static PasspointManager sPasspointManager;
118 
119     private final PasspointEventHandler mPasspointEventHandler;
120     private final WifiInjector mWifiInjector;
121     private final Handler mHandler;
122     private final WifiKeyStore mKeyStore;
123     private final PasspointObjectFactory mObjectFactory;
124 
125     private final Map<String, PasspointProvider> mProviders;
126     private final AnqpCache mAnqpCache;
127     private final ANQPRequestManager mAnqpRequestManager;
128     private final WifiConfigManager mWifiConfigManager;
129     private final WifiMetrics mWifiMetrics;
130     private final PasspointProvisioner mPasspointProvisioner;
131     private final AppOpsManager mAppOps;
132     private final WifiCarrierInfoManager mWifiCarrierInfoManager;
133     private final MacAddressUtil mMacAddressUtil;
134     private final Clock mClock;
135     private final WifiPermissionsUtil mWifiPermissionsUtil;
136 
137     /**
138      * Map of package name of an app to the app ops changed listener for the app.
139      */
140     private final Map<String, AppOpsChangedListener> mAppOpsChangedListenerPerApp = new HashMap<>();
141 
142     // Counter used for assigning unique identifier to each provider.
143     private long mProviderIndex;
144     private boolean mVerboseLoggingEnabled = false;
145 
146     private class CallbackHandler implements PasspointEventHandler.Callbacks {
147         private final Context mContext;
CallbackHandler(Context context)148         CallbackHandler(Context context) {
149             mContext = context;
150         }
151 
152         @Override
onANQPResponse(long bssid, Map<Constants.ANQPElementType, ANQPElement> anqpElements)153         public void onANQPResponse(long bssid,
154                 Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
155             if (mVerboseLoggingEnabled) {
156                 Log.d(TAG, "ANQP response received from BSSID "
157                         + Utils.macToString(bssid) + " - List of ANQP elements:");
158                 int i = 0;
159                 for (Constants.ANQPElementType type : anqpElements.keySet()) {
160                     Log.d(TAG, "#" + i++ + ": " + type);
161                 }
162             }
163             // Notify request manager for the completion of a request.
164             ANQPNetworkKey anqpKey =
165                     mAnqpRequestManager.onRequestCompleted(bssid, anqpElements != null);
166             if (anqpElements == null || anqpKey == null) {
167                 // Query failed or the request wasn't originated from us (not tracked by the
168                 // request manager). Nothing to be done.
169                 return;
170             }
171 
172             if (anqpElements.containsKey(Constants.ANQPElementType.ANQPVenueUrl)) {
173                 // Venue URL ANQP is requested and received only after the network is connected
174                 mWifiMetrics.incrementTotalNumberOfPasspointConnectionsWithVenueUrl();
175             }
176 
177             // Add new entry to the cache.
178             mAnqpCache.addOrUpdateEntry(anqpKey, anqpElements);
179         }
180 
181         @Override
onIconResponse(long bssid, String fileName, byte[] data)182         public void onIconResponse(long bssid, String fileName, byte[] data) {
183             // Empty
184         }
185 
186         @Override
onWnmFrameReceived(WnmData event)187         public void onWnmFrameReceived(WnmData event) {
188             // Empty
189         }
190     }
191 
192     /**
193      * Data provider for the Passpoint configuration store data
194      * {@link PasspointConfigUserStoreData}.
195      */
196     private class UserDataSourceHandler implements PasspointConfigUserStoreData.DataSource {
197         @Override
getProviders()198         public List<PasspointProvider> getProviders() {
199             List<PasspointProvider> providers = new ArrayList<>();
200             for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
201                 providers.add(entry.getValue());
202             }
203             return providers;
204         }
205 
206         @Override
setProviders(List<PasspointProvider> providers)207         public void setProviders(List<PasspointProvider> providers) {
208             mProviders.clear();
209             for (PasspointProvider provider : providers) {
210                 provider.enableVerboseLogging(mVerboseLoggingEnabled);
211                 mProviders.put(provider.getConfig().getUniqueId(), provider);
212                 if (provider.getPackageName() != null) {
213                     startTrackingAppOpsChange(provider.getPackageName(),
214                             provider.getCreatorUid());
215                 }
216             }
217         }
218     }
219 
220     /**
221      * Data provider for the Passpoint configuration store data
222      * {@link PasspointConfigSharedStoreData}.
223      */
224     private class SharedDataSourceHandler implements PasspointConfigSharedStoreData.DataSource {
225         @Override
getProviderIndex()226         public long getProviderIndex() {
227             return mProviderIndex;
228         }
229 
230         @Override
setProviderIndex(long providerIndex)231         public void setProviderIndex(long providerIndex) {
232             mProviderIndex = providerIndex;
233         }
234     }
235 
236     /**
237      * Listener for app-ops changes for apps to remove the corresponding Passpoint profiles.
238      */
239     private final class AppOpsChangedListener implements AppOpsManager.OnOpChangedListener {
240         private final String mPackageName;
241         private final int mUid;
242 
AppOpsChangedListener(@onNull String packageName, int uid)243         AppOpsChangedListener(@NonNull String packageName, int uid) {
244             mPackageName = packageName;
245             mUid = uid;
246         }
247 
248         @Override
onOpChanged(String op, String packageName)249         public void onOpChanged(String op, String packageName) {
250             mHandler.post(() -> {
251                 if (!mPackageName.equals(packageName)) return;
252                 if (!OPSTR_CHANGE_WIFI_STATE.equals(op)) return;
253 
254                 // Ensures the uid to package mapping is still correct.
255                 try {
256                     mAppOps.checkPackage(mUid, mPackageName);
257                 } catch (SecurityException e) {
258                     Log.wtf(TAG, "Invalid uid/package" + packageName);
259                     return;
260                 }
261                 if (mAppOps.unsafeCheckOpNoThrow(OPSTR_CHANGE_WIFI_STATE, mUid, mPackageName)
262                         == AppOpsManager.MODE_IGNORED) {
263                     Log.i(TAG, "User disallowed change wifi state for " + packageName);
264 
265                     // Removes the profiles installed by the app from database.
266                     removePasspointProviderWithPackage(mPackageName);
267                 }
268             });
269         }
270     }
271 
272     private class OnNetworkUpdateListener implements
273             WifiConfigManager.OnNetworkUpdateListener {
274         @Override
onConnectChoiceSet(@onNull List<WifiConfiguration> networks, String choiceKey, int rssi)275         public void onConnectChoiceSet(@NonNull List<WifiConfiguration> networks,
276                 String choiceKey, int rssi) {
277             onUserConnectChoiceSet(networks, choiceKey, rssi);
278         }
279         @Override
onConnectChoiceRemoved(String choiceKey)280         public void onConnectChoiceRemoved(String choiceKey) {
281             onUserConnectChoiceRemove(choiceKey);
282         }
283 
284     }
285 
onUserConnectChoiceRemove(String choiceKey)286     private void onUserConnectChoiceRemove(String choiceKey) {
287         mProviders.values().stream()
288                 .filter(provider -> TextUtils.equals(provider.getConnectChoice(), choiceKey))
289                 .forEach(provider -> {
290                     provider.setUserConnectChoice(null, 0);
291                 });
292         mWifiConfigManager.saveToStore(true);
293     }
294 
onUserConnectChoiceSet(List<WifiConfiguration> networks, String choiceKey, int rssi)295     private void onUserConnectChoiceSet(List<WifiConfiguration> networks, String choiceKey,
296             int rssi) {
297         for (WifiConfiguration config : networks) {
298             PasspointProvider provider = mProviders.get(config.getProfileKey());
299             if (provider != null) {
300                 provider.setUserConnectChoice(choiceKey, rssi);
301             }
302         }
303         PasspointProvider provider = mProviders.get(choiceKey);
304         if (provider != null) {
305             provider.setUserConnectChoice(null, 0);
306         }
307         mWifiConfigManager.saveToStore(true);
308     }
309 
310     /**
311      * Remove all Passpoint profiles installed by the app that has been disabled or uninstalled.
312      *
313      * @param packageName Package name of the app to remove the corresponding Passpoint profiles.
314      */
removePasspointProviderWithPackage(@onNull String packageName)315     public void removePasspointProviderWithPackage(@NonNull String packageName) {
316         stopTrackingAppOpsChange(packageName);
317         for (Map.Entry<String, PasspointProvider> entry : getPasspointProviderWithPackage(
318                 packageName).entrySet()) {
319             String uniqueId = entry.getValue().getConfig().getUniqueId();
320             removeProvider(Process.WIFI_UID /* ignored */, true, uniqueId, null);
321         }
322     }
323 
getPasspointProviderWithPackage( @onNull String packageName)324     private Map<String, PasspointProvider> getPasspointProviderWithPackage(
325             @NonNull String packageName) {
326         return mProviders.entrySet().stream().filter(
327                 entry -> TextUtils.equals(packageName,
328                         entry.getValue().getPackageName())).collect(
329                 Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue()));
330     }
331 
startTrackingAppOpsChange(@onNull String packageName, int uid)332     private void startTrackingAppOpsChange(@NonNull String packageName, int uid) {
333         // The package is already registered.
334         if (mAppOpsChangedListenerPerApp.containsKey(packageName)) return;
335         AppOpsChangedListener appOpsChangedListener = new AppOpsChangedListener(packageName, uid);
336         mAppOps.startWatchingMode(OPSTR_CHANGE_WIFI_STATE, packageName, appOpsChangedListener);
337         mAppOpsChangedListenerPerApp.put(packageName, appOpsChangedListener);
338     }
339 
stopTrackingAppOpsChange(@onNull String packageName)340     private void stopTrackingAppOpsChange(@NonNull String packageName) {
341         AppOpsChangedListener appOpsChangedListener = mAppOpsChangedListenerPerApp.remove(
342                 packageName);
343         if (appOpsChangedListener == null) {
344             Log.i(TAG, "No app ops listener found for " + packageName);
345             return;
346         }
347         mAppOps.stopWatchingMode(appOpsChangedListener);
348     }
349 
PasspointManager(Context context, WifiInjector wifiInjector, Handler handler, WifiNative wifiNative, WifiKeyStore keyStore, Clock clock, PasspointObjectFactory objectFactory, WifiConfigManager wifiConfigManager, WifiConfigStore wifiConfigStore, WifiMetrics wifiMetrics, WifiCarrierInfoManager wifiCarrierInfoManager, MacAddressUtil macAddressUtil, WifiPermissionsUtil wifiPermissionsUtil)350     public PasspointManager(Context context, WifiInjector wifiInjector, Handler handler,
351             WifiNative wifiNative, WifiKeyStore keyStore, Clock clock,
352             PasspointObjectFactory objectFactory, WifiConfigManager wifiConfigManager,
353             WifiConfigStore wifiConfigStore,
354             WifiMetrics wifiMetrics,
355             WifiCarrierInfoManager wifiCarrierInfoManager,
356             MacAddressUtil macAddressUtil,
357             WifiPermissionsUtil wifiPermissionsUtil) {
358         mPasspointEventHandler = objectFactory.makePasspointEventHandler(wifiInjector,
359                 new CallbackHandler(context));
360         mWifiInjector = wifiInjector;
361         mHandler = handler;
362         mKeyStore = keyStore;
363         mObjectFactory = objectFactory;
364         mProviders = new HashMap<>();
365         mAnqpCache = objectFactory.makeAnqpCache(clock);
366         mAnqpRequestManager = objectFactory.makeANQPRequestManager(mPasspointEventHandler, clock);
367         mWifiConfigManager = wifiConfigManager;
368         mWifiMetrics = wifiMetrics;
369         mProviderIndex = 0;
370         mWifiCarrierInfoManager = wifiCarrierInfoManager;
371         wifiConfigStore.registerStoreData(objectFactory.makePasspointConfigUserStoreData(
372                 mKeyStore, mWifiCarrierInfoManager, new UserDataSourceHandler(), clock));
373         wifiConfigStore.registerStoreData(objectFactory.makePasspointConfigSharedStoreData(
374                 new SharedDataSourceHandler()));
375         mPasspointProvisioner = objectFactory.makePasspointProvisioner(context, wifiNative,
376                 this, wifiMetrics);
377         mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
378         sPasspointManager = this;
379         mMacAddressUtil = macAddressUtil;
380         mClock = clock;
381         mWifiConfigManager.addOnNetworkUpdateListener(
382                 new PasspointManager.OnNetworkUpdateListener());
383         mWifiPermissionsUtil = wifiPermissionsUtil;
384     }
385 
386     /**
387      * Initializes the provisioning flow with a looper.
388      * This looper should be tied to a background worker thread since PasspointProvisioner has a
389      * heavy workload.
390      */
initializeProvisioner(Looper looper)391     public void initializeProvisioner(Looper looper) {
392         mPasspointProvisioner.init(looper);
393     }
394 
395     /**
396      * Enable verbose logging
397      * @param verbose enables verbose logging
398      */
enableVerboseLogging(boolean verbose)399     public void enableVerboseLogging(boolean verbose) {
400         mVerboseLoggingEnabled = verbose;
401         mPasspointProvisioner.enableVerboseLogging(verbose);
402         for (PasspointProvider provider : mProviders.values()) {
403             provider.enableVerboseLogging(verbose);
404         }
405     }
406 
updateWifiConfigInWcmIfPresent( WifiConfiguration newConfig, int uid, String packageName, boolean isFromSuggestion)407     private void updateWifiConfigInWcmIfPresent(
408             WifiConfiguration newConfig, int uid, String packageName, boolean isFromSuggestion) {
409         WifiConfiguration configInWcm =
410                 mWifiConfigManager.getConfiguredNetwork(newConfig.getProfileKey());
411         if (configInWcm == null) return;
412         // suggestion != saved
413         if (isFromSuggestion != configInWcm.fromWifiNetworkSuggestion) return;
414         // is suggestion from same app.
415         if (isFromSuggestion
416                 && (configInWcm.creatorUid != uid
417                 || !TextUtils.equals(configInWcm.creatorName, packageName))) {
418             return;
419         }
420         NetworkUpdateResult result = mWifiConfigManager.addOrUpdateNetwork(
421                 newConfig, uid, packageName);
422         if (!result.isSuccess()) {
423             Log.e(TAG, "Failed to update config in WifiConfigManager");
424         } else {
425             mWifiConfigManager.allowAutojoin(result.getNetworkId(), newConfig.allowAutojoin);
426             if (mVerboseLoggingEnabled) {
427                 Log.v(TAG, "Updated config in WifiConfigManager");
428             }
429         }
430     }
431 
432     /**
433      * Add or update a Passpoint provider with the given configuration.
434      *
435      * Each provider is uniquely identified by its unique identifier, see
436      * {@link PasspointConfiguration#getUniqueId()}.
437      * In the case when there is an existing configuration with the same unique identifier,
438      * a provider with the new configuration will replace the existing provider.
439      *
440      * @param config Configuration of the Passpoint provider to be added
441      * @param uid Uid of the app adding/Updating {@code config}
442      * @param packageName Package name of the app adding/Updating {@code config}
443      * @param isFromSuggestion Whether this {@code config} is from suggestion API
444      * @param isTrusted Whether this {@code config} an trusted network, default should be true.
445      *                  Only able set to false when {@code isFromSuggestion} is true, otherwise
446      *                  adding {@code config} will be false.
447      * @return true if provider is added, false otherwise
448      */
addOrUpdateProvider(PasspointConfiguration config, int uid, String packageName, boolean isFromSuggestion, boolean isTrusted)449     public boolean addOrUpdateProvider(PasspointConfiguration config, int uid,
450             String packageName, boolean isFromSuggestion, boolean isTrusted) {
451         mWifiMetrics.incrementNumPasspointProviderInstallation();
452         if (config == null) {
453             Log.e(TAG, "Configuration not provided");
454             return false;
455         }
456         if (!config.validate()) {
457             Log.e(TAG, "Invalid configuration");
458             return false;
459         }
460         if (!(isFromSuggestion || isTrusted)) {
461             Log.e(TAG, "Set isTrusted to false on a non suggestion passpoint is not allowed");
462             return false;
463         }
464         if (!mWifiPermissionsUtil.doesUidBelongToCurrentUser(uid)) {
465             Log.e(TAG, "UID " + uid + " not visible to the current user");
466             return false;
467         }
468 
469         mWifiCarrierInfoManager.tryUpdateCarrierIdForPasspoint(config);
470         // Create a provider and install the necessary certificates and keys.
471         PasspointProvider newProvider = mObjectFactory.makePasspointProvider(config, mKeyStore,
472                 mWifiCarrierInfoManager, mProviderIndex++, uid, packageName, isFromSuggestion,
473                 mClock);
474         newProvider.setTrusted(isTrusted);
475 
476         boolean metricsNoRootCa = false;
477         boolean metricsSelfSignedRootCa = false;
478         boolean metricsSubscriptionExpiration = false;
479 
480         if (config.getCredential().getUserCredential() != null
481                 || config.getCredential().getCertCredential() != null) {
482             X509Certificate[] x509Certificates = config.getCredential().getCaCertificates();
483             if (x509Certificates == null) {
484                 metricsNoRootCa = true;
485             } else {
486                 try {
487                     for (X509Certificate certificate : x509Certificates) {
488                         verifyCaCert(certificate);
489                     }
490                 } catch (CertPathValidatorException e) {
491                     // A self signed Root CA will fail path validation checks with NO_TRUST_ANCHOR
492                     if (e.getReason() == NO_TRUST_ANCHOR) {
493                         metricsSelfSignedRootCa = true;
494                     }
495                 } catch (Exception e) {
496                     // Other exceptions, fall through, will be handled below
497                 }
498             }
499         }
500         if (config.getSubscriptionExpirationTimeMillis() != Long.MIN_VALUE) {
501             metricsSubscriptionExpiration = true;
502         }
503 
504         if (!newProvider.installCertsAndKeys()) {
505             Log.e(TAG, "Failed to install certificates and keys to keystore");
506             return false;
507         }
508 
509         // Remove existing provider with the same unique ID.
510         if (mProviders.containsKey(config.getUniqueId())) {
511             PasspointProvider old = mProviders.get(config.getUniqueId());
512             // If new profile is from suggestion and from a different App, ignore new profile,
513             // return false.
514             // If from same app, update it.
515             if (isFromSuggestion && !old.getPackageName().equals(packageName)) {
516                 newProvider.uninstallCertsAndKeys();
517                 return false;
518             }
519             Log.d(TAG, "Replacing configuration for FQDN: " + config.getHomeSp().getFqdn()
520                     + " and unique ID: " + config.getUniqueId());
521             old.uninstallCertsAndKeys();
522             mProviders.remove(config.getUniqueId());
523             // Keep the user connect choice and AnonymousIdentity
524             newProvider.setUserConnectChoice(old.getConnectChoice(), old.getConnectChoiceRssi());
525             newProvider.setAnonymousIdentity(old.getAnonymousIdentity());
526             // New profile changes the credential, remove the related WifiConfig.
527             if (!old.equals(newProvider)) {
528                 mWifiConfigManager.removePasspointConfiguredNetwork(
529                         newProvider.getWifiConfig().getProfileKey());
530             } else {
531                 // If there is a config cached in WifiConfigManager, update it with new info.
532                 updateWifiConfigInWcmIfPresent(
533                         newProvider.getWifiConfig(), uid, packageName, isFromSuggestion);
534             }
535         }
536         newProvider.enableVerboseLogging(mVerboseLoggingEnabled);
537         mProviders.put(config.getUniqueId(), newProvider);
538         mWifiConfigManager.saveToStore(true /* forceWrite */);
539         if (!isFromSuggestion && newProvider.getPackageName() != null) {
540             startTrackingAppOpsChange(newProvider.getPackageName(), uid);
541         }
542         Log.d(TAG, "Added/updated Passpoint configuration for FQDN: "
543                 + config.getHomeSp().getFqdn() + " with unique ID: " + config.getUniqueId()
544                 + " by UID: " + uid);
545         if (metricsNoRootCa) {
546             mWifiMetrics.incrementNumPasspointProviderWithNoRootCa();
547         }
548         if (metricsSelfSignedRootCa) {
549             mWifiMetrics.incrementNumPasspointProviderWithSelfSignedRootCa();
550         }
551         if (metricsSubscriptionExpiration) {
552             mWifiMetrics.incrementNumPasspointProviderWithSubscriptionExpiration();
553         }
554         if (SdkLevel.isAtLeastS() && config.getDecoratedIdentityPrefix() != null) {
555             mWifiMetrics.incrementTotalNumberOfPasspointProfilesWithDecoratedIdentity();
556         }
557         mWifiMetrics.incrementNumPasspointProviderInstallSuccess();
558         return true;
559     }
560 
removeProviderInternal(PasspointProvider provider, int callingUid, boolean privileged)561     private boolean removeProviderInternal(PasspointProvider provider, int callingUid,
562             boolean privileged) {
563         if (!privileged && callingUid != provider.getCreatorUid()) {
564             Log.e(TAG, "UID " + callingUid + " cannot remove profile created by "
565                     + provider.getCreatorUid());
566             return false;
567         }
568         if (!mWifiPermissionsUtil.doesUidBelongToCurrentUser(callingUid)) {
569             Log.e(TAG, "UID " + callingUid + " not visible to the current user");
570             return false;
571         }
572         provider.uninstallCertsAndKeys();
573         String packageName = provider.getPackageName();
574         // Remove any configs corresponding to the profile in WifiConfigManager.
575         mWifiConfigManager.removePasspointConfiguredNetwork(
576                 provider.getWifiConfig().getProfileKey());
577         String uniqueId = provider.getConfig().getUniqueId();
578         mProviders.remove(uniqueId);
579         mWifiConfigManager.removeConnectChoiceFromAllNetworks(uniqueId);
580         mWifiConfigManager.saveToStore(true /* forceWrite */);
581 
582         // Stop monitoring the package if there is no Passpoint profile installed by the package
583         if (mAppOpsChangedListenerPerApp.containsKey(packageName)
584                 && getPasspointProviderWithPackage(packageName).size() == 0) {
585             stopTrackingAppOpsChange(packageName);
586         }
587         Log.d(TAG, "Removed Passpoint configuration: " + uniqueId);
588         mWifiMetrics.incrementNumPasspointProviderUninstallSuccess();
589         return true;
590     }
591 
592     /**
593      * Remove a Passpoint provider identified by the given its unique identifier.
594      *
595      * @param callingUid Calling UID.
596      * @param privileged Whether the caller is a privileged entity
597      * @param uniqueId The ID of the provider to remove. Not required if FQDN is specified.
598      * @param fqdn The FQDN of the provider to remove. Not required if unique ID is specified.
599      * @return true if a provider is removed, false otherwise
600      */
removeProvider(int callingUid, boolean privileged, String uniqueId, String fqdn)601     public boolean removeProvider(int callingUid, boolean privileged, String uniqueId,
602             String fqdn) {
603         if (uniqueId == null && fqdn == null) {
604             mWifiMetrics.incrementNumPasspointProviderUninstallation();
605             Log.e(TAG, "Cannot remove provider, both FQDN and unique ID are null");
606             return false;
607         }
608 
609         if (uniqueId != null) {
610             // Unique identifier provided
611             mWifiMetrics.incrementNumPasspointProviderUninstallation();
612             PasspointProvider provider = mProviders.get(uniqueId);
613             if (provider == null) {
614                 Log.e(TAG, "Config doesn't exist");
615                 return false;
616             }
617             return removeProviderInternal(provider, callingUid, privileged);
618         }
619 
620         // FQDN provided, loop through all profiles with matching FQDN
621         ArrayList<PasspointProvider> passpointProviders = new ArrayList<>(mProviders.values());
622         int removedProviders = 0;
623         int numOfUninstallations = 0;
624         for (PasspointProvider provider : passpointProviders) {
625             if (!TextUtils.equals(provider.getConfig().getHomeSp().getFqdn(), fqdn)) {
626                 continue;
627             }
628             mWifiMetrics.incrementNumPasspointProviderUninstallation();
629             numOfUninstallations++;
630             if (removeProviderInternal(provider, callingUid, privileged)) {
631                 removedProviders++;
632             }
633         }
634 
635         if (numOfUninstallations == 0) {
636             // Update uninstallation requests metrics here to cover the corner case of trying to
637             // uninstall a non-existent provider.
638             mWifiMetrics.incrementNumPasspointProviderUninstallation();
639         }
640 
641         return removedProviders > 0;
642     }
643 
644     /**
645      * Enable or disable the auto-join configuration. Auto-join controls whether or not the
646      * passpoint configuration is used for auto connection (network selection). Note that even
647      * when auto-join is disabled the configuration can still be used for manual connection.
648      *
649      * @param uniqueId The unique identifier of the configuration. Not required if FQDN is specified
650      * @param fqdn The FQDN of the configuration. Not required if uniqueId is specified.
651      * @param enableAutojoin true to enable auto-join, false to disable.
652      * @return true on success, false otherwise (e.g. if no such provider exists).
653      */
enableAutojoin(String uniqueId, String fqdn, boolean enableAutojoin)654     public boolean enableAutojoin(String uniqueId, String fqdn, boolean enableAutojoin) {
655         if (uniqueId == null && fqdn == null) {
656             return false;
657         }
658         if (uniqueId != null) {
659             // Unique identifier provided
660             PasspointProvider provider = mProviders.get(uniqueId);
661             if (provider == null) {
662                 Log.e(TAG, "Config doesn't exist");
663                 return false;
664             }
665             if (provider.setAutojoinEnabled(enableAutojoin)) {
666                 mWifiMetrics.logUserActionEvent(enableAutojoin
667                                 ? UserActionEvent.EVENT_CONFIGURE_AUTO_CONNECT_ON
668                                 : UserActionEvent.EVENT_CONFIGURE_AUTO_CONNECT_OFF,
669                         provider.isFromSuggestion(), true);
670                 // Update WifiConfigManager if changed.
671                 updateWifiConfigInWcmIfPresent(provider.getWifiConfig(), provider.getCreatorUid(),
672                         provider.getPackageName(), provider.isFromSuggestion());
673             }
674 
675             mWifiConfigManager.saveToStore(true);
676             return true;
677         }
678 
679         ArrayList<PasspointProvider> passpointProviders = new ArrayList<>(mProviders.values());
680         boolean found = false;
681 
682         // FQDN provided, loop through all profiles with matching FQDN
683         for (PasspointProvider provider : passpointProviders) {
684             if (TextUtils.equals(provider.getConfig().getHomeSp().getFqdn(), fqdn)) {
685                 if (provider.setAutojoinEnabled(enableAutojoin)) {
686                     mWifiMetrics.logUserActionEvent(enableAutojoin
687                                     ? UserActionEvent.EVENT_CONFIGURE_AUTO_CONNECT_ON
688                                     : UserActionEvent.EVENT_CONFIGURE_AUTO_CONNECT_OFF,
689                             provider.isFromSuggestion(), true);
690                     // Update WifiConfigManager if changed.
691                     updateWifiConfigInWcmIfPresent(provider.getWifiConfig(),
692                             provider.getCreatorUid(), provider.getPackageName(),
693                             provider.isFromSuggestion());
694                 }
695                 found = true;
696             }
697         }
698         if (found) {
699             mWifiConfigManager.saveToStore(true);
700         }
701         return found;
702     }
703 
704     /**
705      * Enable or disable MAC randomization for this passpoint profile.
706      * @param fqdn The FQDN of the configuration
707      * @param enable true to enable MAC randomization, false to disable
708      * @return true on success, false otherwise (e.g. if no such provider exists).
709      */
enableMacRandomization(@onNull String fqdn, boolean enable)710     public boolean enableMacRandomization(@NonNull String fqdn, boolean enable) {
711         ArrayList<PasspointProvider> passpointProviders = new ArrayList<>(mProviders.values());
712         boolean found = false;
713 
714         // Loop through all profiles with matching FQDN
715         for (PasspointProvider provider : passpointProviders) {
716             if (TextUtils.equals(provider.getConfig().getHomeSp().getFqdn(), fqdn)) {
717                 boolean settingChanged = provider.setMacRandomizationEnabled(enable);
718                 if (settingChanged) {
719                     mWifiMetrics.logUserActionEvent(enable
720                                     ? UserActionEvent.EVENT_CONFIGURE_MAC_RANDOMIZATION_ON
721                                     : UserActionEvent.EVENT_CONFIGURE_MAC_RANDOMIZATION_OFF,
722                             provider.isFromSuggestion(), true);
723                     mWifiConfigManager.removePasspointConfiguredNetwork(
724                             provider.getWifiConfig().getProfileKey());
725                 }
726                 found = true;
727             }
728         }
729         if (found) {
730             mWifiConfigManager.saveToStore(true);
731         }
732         return found;
733     }
734 
735     /**
736      * Set the metered override value for this passpoint profile
737      * @param fqdn The FQDN of the configuration
738      * @param meteredOverride One of the values in {@link MeteredOverride}
739      * @return true on success, false otherwise (e.g. if no such provider exists).
740      */
setMeteredOverride(@onNull String fqdn, @MeteredOverride int meteredOverride)741     public boolean setMeteredOverride(@NonNull String fqdn, @MeteredOverride int meteredOverride) {
742         ArrayList<PasspointProvider> passpointProviders = new ArrayList<>(mProviders.values());
743         boolean found = false;
744 
745         // Loop through all profiles with matching FQDN
746         for (PasspointProvider provider : passpointProviders) {
747             if (TextUtils.equals(provider.getConfig().getHomeSp().getFqdn(), fqdn)) {
748                 if (provider.setMeteredOverride(meteredOverride)) {
749                     mWifiMetrics.logUserActionEvent(
750                             WifiMetrics.convertMeteredOverrideEnumToUserActionEventType(
751                                     meteredOverride),
752                             provider.isFromSuggestion(), true);
753                 }
754                 found = true;
755             }
756         }
757         if (found) {
758             mWifiConfigManager.saveToStore(true);
759         }
760         return found;
761     }
762 
763     /**
764      * Return the installed Passpoint provider configurations.
765      * An empty list will be returned when no provider is installed.
766      *
767      * @param callingUid Calling UID.
768      * @param privileged Whether the caller is a privileged entity
769      * @return A list of {@link PasspointConfiguration}
770      */
getProviderConfigs(int callingUid, boolean privileged)771     public List<PasspointConfiguration> getProviderConfigs(int callingUid,
772             boolean privileged) {
773         List<PasspointConfiguration> configs = new ArrayList<>();
774         for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
775             PasspointProvider provider = entry.getValue();
776             if (privileged || callingUid == provider.getCreatorUid()) {
777                 if (provider.isFromSuggestion()) {
778                     continue;
779                 }
780                 configs.add(provider.getConfig());
781             }
782         }
783         return configs;
784     }
785 
786     /**
787      * Find all providers that can provide service through the given AP, which means the
788      * providers contained credential to authenticate with the given AP.
789      *
790      * If there is any home provider available, will return a list of matched home providers.
791      * Otherwise will return a list of matched roaming providers.
792      *
793      * A empty list will be returned if no matching is found.
794      *
795      * @param scanResult The scan result associated with the AP
796      * @return a list of pairs of {@link PasspointProvider} and match status.
797      */
matchProvider( ScanResult scanResult)798     public @NonNull List<Pair<PasspointProvider, PasspointMatch>> matchProvider(
799             ScanResult scanResult) {
800         return matchProvider(scanResult, true);
801     }
802 
803     /**
804      * Find all providers that can provide service through the given AP, which means the
805      * providers contained credential to authenticate with the given AP.
806      *
807      * If there is any home provider available, will return a list of matched home providers.
808      * Otherwise will return a list of matched roaming providers.
809      *
810      * A empty list will be returned if no matching is found.
811      *
812      * @param scanResult The scan result associated with the AP
813      * @param anqpRequestAllowed Indicates if to allow ANQP request if the provider's entry is empty
814      * @return a list of pairs of {@link PasspointProvider} and match status.
815      */
matchProvider( ScanResult scanResult, boolean anqpRequestAllowed)816     public @NonNull List<Pair<PasspointProvider, PasspointMatch>> matchProvider(
817             ScanResult scanResult, boolean anqpRequestAllowed) {
818         List<Pair<PasspointProvider, PasspointMatch>> allMatches = getAllMatchedProviders(
819                 scanResult, anqpRequestAllowed);
820         if (allMatches.isEmpty()) {
821             return allMatches;
822         }
823         List<Pair<PasspointProvider, PasspointMatch>> homeProviders = new ArrayList<>();
824         List<Pair<PasspointProvider, PasspointMatch>> roamingProviders = new ArrayList<>();
825         for (Pair<PasspointProvider, PasspointMatch> match : allMatches) {
826             if (isExpired(match.first.getConfig())) {
827                 continue;
828             }
829             if (match.second == PasspointMatch.HomeProvider) {
830                 homeProviders.add(match);
831             } else {
832                 roamingProviders.add(match);
833             }
834         }
835 
836         if (!homeProviders.isEmpty()) {
837             Log.d(TAG, String.format("Matched %s to %s providers as %s", scanResult.SSID,
838                     homeProviders.size(), "Home Provider"));
839             return homeProviders;
840         }
841 
842         if (!roamingProviders.isEmpty()) {
843             Log.d(TAG, String.format("Matched %s to %s providers as %s", scanResult.SSID,
844                     roamingProviders.size(), "Roaming Provider"));
845             return roamingProviders;
846         }
847 
848         if (mVerboseLoggingEnabled) {
849             Log.d(TAG, "No service provider found for " + scanResult.SSID);
850         }
851         return new ArrayList<>();
852     }
853 
854     /**
855      * Return a list of all providers that can provide service through the given AP.
856      *
857      * @param scanResult The scan result associated with the AP
858      * @return a list of pairs of {@link PasspointProvider} and match status.
859      */
getAllMatchedProviders( ScanResult scanResult)860     public @NonNull List<Pair<PasspointProvider, PasspointMatch>> getAllMatchedProviders(
861             ScanResult scanResult) {
862         return getAllMatchedProviders(scanResult, true);
863     }
864 
865     /**
866      * Return a list of all providers that can provide service through the given AP.
867      *
868      * @param scanResult The scan result associated with the AP
869      * @param anqpRequestAllowed Indicates if to allow ANQP request if the provider's entry is empty
870      * @return a list of pairs of {@link PasspointProvider} and match status.
871      */
getAllMatchedProviders( ScanResult scanResult, boolean anqpRequestAllowed)872     private @NonNull List<Pair<PasspointProvider, PasspointMatch>> getAllMatchedProviders(
873             ScanResult scanResult, boolean anqpRequestAllowed) {
874         List<Pair<PasspointProvider, PasspointMatch>> allMatches = new ArrayList<>();
875 
876         // Retrieve the relevant information elements, mainly Roaming Consortium IE and Hotspot 2.0
877         // Vendor Specific IE.
878         InformationElementUtil.RoamingConsortium roamingConsortium =
879                 InformationElementUtil.getRoamingConsortiumIE(scanResult.informationElements);
880         InformationElementUtil.Vsa vsa = InformationElementUtil.getHS2VendorSpecificIE(
881                 scanResult.informationElements);
882 
883         // Lookup ANQP data in the cache.
884         long bssid;
885         try {
886             bssid = Utils.parseMac(scanResult.BSSID);
887         } catch (IllegalArgumentException e) {
888             Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID);
889             return allMatches;
890         }
891         ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(scanResult.SSID, bssid, scanResult.hessid,
892                 vsa.anqpDomainID);
893         ANQPData anqpEntry = mAnqpCache.getEntry(anqpKey);
894         if (anqpEntry == null) {
895             if (anqpRequestAllowed) {
896                 mAnqpRequestManager.requestANQPElements(bssid, anqpKey,
897                         roamingConsortium.anqpOICount > 0, vsa.hsRelease);
898             }
899             Log.d(TAG, "ANQP entry not found for: " + anqpKey);
900             return allMatches;
901         }
902         boolean anyProviderUpdated = false;
903         for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
904             PasspointProvider provider = entry.getValue();
905             if (provider.tryUpdateCarrierId()) {
906                 anyProviderUpdated = true;
907             }
908             if (mVerboseLoggingEnabled) {
909                 Log.d(TAG, "Matching provider " + provider.getConfig().getHomeSp().getFqdn()
910                         + " with "
911                         + anqpEntry.getElements().get(Constants.ANQPElementType.ANQPDomName));
912             }
913             PasspointMatch matchStatus = provider.match(anqpEntry.getElements(),
914                     roamingConsortium, scanResult);
915             if (matchStatus == PasspointMatch.HomeProvider
916                     || matchStatus == PasspointMatch.RoamingProvider) {
917                 allMatches.add(Pair.create(provider, matchStatus));
918             }
919         }
920         if (anyProviderUpdated) {
921             mWifiConfigManager.saveToStore(true);
922         }
923         if (allMatches.size() != 0) {
924             for (Pair<PasspointProvider, PasspointMatch> match : allMatches) {
925                 Log.d(TAG, String.format("Matched %s to %s as %s", scanResult.SSID,
926                         match.first.getConfig().getHomeSp().getFqdn(),
927                         match.second == PasspointMatch.HomeProvider ? "Home Provider"
928                                 : "Roaming Provider"));
929             }
930         } else {
931             if (mVerboseLoggingEnabled) {
932                 Log.d(TAG, "No service providers found for " + scanResult.SSID);
933             }
934         }
935         return allMatches;
936     }
937 
938     /**
939      * Add a legacy Passpoint configuration represented by a {@link WifiConfiguration} to the
940      * current {@link PasspointManager}.
941      *
942      * This will not trigger a config store write, since this will be invoked as part of the
943      * configuration migration, the caller will be responsible for triggering store write
944      * after the migration is completed.
945      *
946      * @param config {@link WifiConfiguration} representation of the Passpoint configuration
947      * @return true on success
948      */
addLegacyPasspointConfig(WifiConfiguration config)949     public static boolean addLegacyPasspointConfig(WifiConfiguration config) {
950         if (sPasspointManager == null) {
951             Log.e(TAG, "PasspointManager have not been initialized yet");
952             return false;
953         }
954         Log.d(TAG, "Installing legacy Passpoint configuration: " + config.FQDN);
955         return sPasspointManager.addWifiConfig(config);
956     }
957 
958     /**
959      * Sweep the ANQP cache to remove expired entries.
960      */
sweepCache()961     public void sweepCache() {
962         mAnqpCache.sweep();
963     }
964 
965     /**
966      * Notify the completion of an ANQP request.
967      * TODO(zqiu): currently the notification is done through WifiMonitor,
968      * will no longer be the case once we switch over to use wificond.
969      */
notifyANQPDone(AnqpEvent anqpEvent)970     public void notifyANQPDone(AnqpEvent anqpEvent) {
971         mPasspointEventHandler.notifyANQPDone(anqpEvent);
972     }
973 
974     /**
975      * Notify the completion of an icon request.
976      * TODO(zqiu): currently the notification is done through WifiMonitor,
977      * will no longer be the case once we switch over to use wificond.
978      */
notifyIconDone(IconEvent iconEvent)979     public void notifyIconDone(IconEvent iconEvent) {
980         mPasspointEventHandler.notifyIconDone(iconEvent);
981     }
982 
983     /**
984      * Notify the reception of a Wireless Network Management (WNM) frame.
985      */
receivedWnmFrame(WnmData data)986     public void receivedWnmFrame(WnmData data) {
987         mPasspointEventHandler.notifyWnmFrameReceived(data);
988     }
989 
990     /**
991      * Request the specified icon file |fileName| from the specified AP |bssid|.
992      * @return true if the request is sent successfully, false otherwise
993      */
queryPasspointIcon(long bssid, String fileName)994     public boolean queryPasspointIcon(long bssid, String fileName) {
995         return mPasspointEventHandler.requestIcon(bssid, fileName);
996     }
997 
998     /**
999      * Lookup the ANQP elements associated with the given AP from the cache. An empty map
1000      * will be returned if no match found in the cache.
1001      *
1002      * @param scanResult The scan result associated with the AP
1003      * @return Map of ANQP elements
1004      */
getANQPElements(ScanResult scanResult)1005     public Map<Constants.ANQPElementType, ANQPElement> getANQPElements(ScanResult scanResult) {
1006         // Retrieve the Hotspot 2.0 Vendor Specific IE.
1007         InformationElementUtil.Vsa vsa =
1008                 InformationElementUtil.getHS2VendorSpecificIE(scanResult.informationElements);
1009 
1010         // Lookup ANQP data in the cache.
1011         long bssid;
1012         try {
1013             bssid = Utils.parseMac(scanResult.BSSID);
1014         } catch (IllegalArgumentException e) {
1015             Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID);
1016             return new HashMap<>();
1017         }
1018         ANQPData anqpEntry = mAnqpCache.getEntry(ANQPNetworkKey.buildKey(
1019                 scanResult.SSID, bssid, scanResult.hessid, vsa.anqpDomainID));
1020         if (anqpEntry != null) {
1021             return anqpEntry.getElements();
1022         }
1023         return new HashMap<>();
1024     }
1025 
1026     /**
1027      * Return a map of all matching configurations keys with corresponding scanResults (or an empty
1028      * map if none).
1029      *
1030      * @param scanResults The list of scan results
1031      * @return Map that consists of identifies and corresponding scanResults per network type
1032      * ({@link WifiManager#PASSPOINT_HOME_NETWORK}, {@link WifiManager#PASSPOINT_ROAMING_NETWORK}).
1033      */
1034     public Map<String, Map<Integer, List<ScanResult>>>
getAllMatchingPasspointProfilesForScanResults(List<ScanResult> scanResults)1035             getAllMatchingPasspointProfilesForScanResults(List<ScanResult> scanResults) {
1036         if (scanResults == null) {
1037             Log.e(TAG, "Attempt to get matching config for a null ScanResults");
1038             return new HashMap<>();
1039         }
1040         Map<String, Map<Integer, List<ScanResult>>> configs = new HashMap<>();
1041 
1042         for (ScanResult scanResult : scanResults) {
1043             if (!scanResult.isPasspointNetwork()) continue;
1044             List<Pair<PasspointProvider, PasspointMatch>> matchedProviders = getAllMatchedProviders(
1045                     scanResult);
1046             for (Pair<PasspointProvider, PasspointMatch> matchedProvider : matchedProviders) {
1047                 WifiConfiguration config = matchedProvider.first.getWifiConfig();
1048                 int type = WifiManager.PASSPOINT_HOME_NETWORK;
1049                 if (!config.isHomeProviderNetwork) {
1050                     type = WifiManager.PASSPOINT_ROAMING_NETWORK;
1051                 }
1052                 Map<Integer, List<ScanResult>> scanResultsPerNetworkType =
1053                         configs.computeIfAbsent(config.getProfileKey(),
1054                                 k -> new HashMap<>());
1055                 List<ScanResult> matchingScanResults = scanResultsPerNetworkType.computeIfAbsent(
1056                         type, k -> new ArrayList<>());
1057                 matchingScanResults.add(scanResult);
1058             }
1059         }
1060 
1061         return configs;
1062     }
1063 
1064     /**
1065      * Returns the list of Hotspot 2.0 OSU (Online Sign-Up) providers associated with the given list
1066      * of ScanResult.
1067      *
1068      * An empty map will be returned when an invalid scanResults are provided or no match is found.
1069      *
1070      * @param scanResults a list of ScanResult that has Passpoint APs.
1071      * @return Map that consists of {@link OsuProvider} and a matching list of {@link ScanResult}
1072      */
getMatchingOsuProviders( List<ScanResult> scanResults)1073     public Map<OsuProvider, List<ScanResult>> getMatchingOsuProviders(
1074             List<ScanResult> scanResults) {
1075         if (scanResults == null) {
1076             Log.e(TAG, "Attempt to retrieve OSU providers for a null ScanResult");
1077             return new HashMap();
1078         }
1079 
1080         Map<OsuProvider, List<ScanResult>> osuProviders = new HashMap<>();
1081         for (ScanResult scanResult : scanResults) {
1082             if (!scanResult.isPasspointNetwork()) continue;
1083 
1084             // Lookup OSU Providers ANQP element.
1085             Map<Constants.ANQPElementType, ANQPElement> anqpElements = getANQPElements(scanResult);
1086             if (!anqpElements.containsKey(Constants.ANQPElementType.HSOSUProviders)) {
1087                 continue;
1088             }
1089             HSOsuProvidersElement element =
1090                     (HSOsuProvidersElement) anqpElements.get(
1091                             Constants.ANQPElementType.HSOSUProviders);
1092             for (OsuProviderInfo info : element.getProviders()) {
1093                 // Set null for OSU-SSID in the class because OSU-SSID is a factor for hotspot
1094                 // operator rather than service provider, which means it can be different for
1095                 // each hotspot operators.
1096                 OsuProvider provider = new OsuProvider((WifiSsid) null, info.getFriendlyNames(),
1097                         info.getServiceDescription(), info.getServerUri(),
1098                         info.getNetworkAccessIdentifier(), info.getMethodList());
1099                 List<ScanResult> matchingScanResults = osuProviders.get(provider);
1100                 if (matchingScanResults == null) {
1101                     matchingScanResults = new ArrayList<>();
1102                     osuProviders.put(provider, matchingScanResults);
1103                 }
1104                 matchingScanResults.add(scanResult);
1105             }
1106         }
1107         return osuProviders;
1108     }
1109 
1110     /**
1111      * Returns the matching Passpoint configurations for given OSU(Online Sign-Up) providers
1112      *
1113      * An empty map will be returned when an invalid {@code osuProviders} are provided or no match
1114      * is found.
1115      *
1116      * @param osuProviders a list of {@link OsuProvider}
1117      * @return Map that consists of {@link OsuProvider} and matching {@link PasspointConfiguration}.
1118      */
getMatchingPasspointConfigsForOsuProviders( List<OsuProvider> osuProviders)1119     public Map<OsuProvider, PasspointConfiguration> getMatchingPasspointConfigsForOsuProviders(
1120             List<OsuProvider> osuProviders) {
1121         Map<OsuProvider, PasspointConfiguration> matchingPasspointConfigs = new HashMap<>();
1122 
1123         for (OsuProvider osuProvider : osuProviders) {
1124             Map<String, String> friendlyNamesForOsuProvider = osuProvider.getFriendlyNameList();
1125             if (friendlyNamesForOsuProvider == null) continue;
1126             for (PasspointProvider provider : mProviders.values()) {
1127                 PasspointConfiguration passpointConfiguration = provider.getConfig();
1128                 Map<String, String> serviceFriendlyNamesForPpsMo =
1129                         passpointConfiguration.getServiceFriendlyNames();
1130                 if (serviceFriendlyNamesForPpsMo == null) continue;
1131 
1132                 for (Map.Entry<String, String> entry : serviceFriendlyNamesForPpsMo.entrySet()) {
1133                     String lang = entry.getKey();
1134                     String friendlyName = entry.getValue();
1135                     if (friendlyName == null) continue;
1136                     String osuFriendlyName = friendlyNamesForOsuProvider.get(lang);
1137                     if (osuFriendlyName == null) continue;
1138                     if (friendlyName.equals(osuFriendlyName)) {
1139                         matchingPasspointConfigs.put(osuProvider, passpointConfiguration);
1140                         break;
1141                     }
1142                 }
1143             }
1144         }
1145         return matchingPasspointConfigs;
1146     }
1147 
1148     /**
1149      * Returns the corresponding wifi configurations from {@link WifiConfigManager} for given a list
1150      * of Passpoint profile unique identifiers.
1151      *
1152      * Note: Not all matched Passpoint profile's WifiConfiguration will be returned, only the ones
1153      * already be added into the {@link WifiConfigManager} will be returned. As the returns of this
1154      * method is expected to show in Wifi Picker or use with
1155      * {@link WifiManager#connect(int, WifiManager.ActionListener)} API, each WifiConfiguration must
1156      * have a valid network Id.
1157      *
1158      * An empty list will be returned when no match is found.
1159      *
1160      * @param idList a list of unique identifiers
1161      * @return List of {@link WifiConfiguration} converted from {@link PasspointProvider}
1162      */
getWifiConfigsForPasspointProfiles(List<String> idList)1163     public List<WifiConfiguration> getWifiConfigsForPasspointProfiles(List<String> idList) {
1164         if (mProviders.isEmpty()) {
1165             return Collections.emptyList();
1166         }
1167         List<WifiConfiguration> configs = new ArrayList<>();
1168         Set<String> uniqueIdSet = new HashSet<>();
1169         uniqueIdSet.addAll(idList);
1170         for (String uniqueId : uniqueIdSet) {
1171             PasspointProvider provider = mProviders.get(uniqueId);
1172             if (provider == null) {
1173                 continue;
1174             }
1175             WifiConfiguration config = provider.getWifiConfig();
1176             config = mWifiConfigManager.getConfiguredNetwork(config.getProfileKey());
1177             if (config == null) {
1178                 continue;
1179             }
1180             // If the Passpoint configuration is from a suggestion, check if the app shares this
1181             // suggestion with the user.
1182             if (provider.isFromSuggestion()
1183                     && !mWifiInjector.getWifiNetworkSuggestionsManager()
1184                     .isPasspointSuggestionSharedWithUser(config)) {
1185                 continue;
1186             }
1187             if (mWifiConfigManager.shouldUseEnhancedRandomization(config)) {
1188                 config.setRandomizedMacAddress(MacAddress.fromString(DEFAULT_MAC_ADDRESS));
1189             } else {
1190                 MacAddress result = mMacAddressUtil.calculatePersistentMac(config.getNetworkKey(),
1191                         mMacAddressUtil.obtainMacRandHashFunction(Process.WIFI_UID));
1192                 if (result != null) {
1193                     config.setRandomizedMacAddress(result);
1194                 }
1195             }
1196             configs.add(config);
1197         }
1198         return configs;
1199     }
1200 
1201     /**
1202      * Invoked when a Passpoint network was successfully connected based on the credentials
1203      * provided by the given Passpoint provider
1204      *
1205      * @param uniqueId The unique identifier of the Passpointprofile
1206      */
onPasspointNetworkConnected(String uniqueId)1207     public void onPasspointNetworkConnected(String uniqueId) {
1208         PasspointProvider provider = mProviders.get(uniqueId);
1209         if (provider == null) {
1210             Log.e(TAG, "Passpoint network connected without provider: " + uniqueId);
1211             return;
1212         }
1213         if (!provider.getHasEverConnected()) {
1214             // First successful connection using this provider.
1215             provider.setHasEverConnected(true);
1216         }
1217     }
1218 
1219     /**
1220      * Update metrics related to installed Passpoint providers, this includes the number of
1221      * installed providers and the number of those providers that results in a successful network
1222      * connection.
1223      */
updateMetrics()1224     public void updateMetrics() {
1225         int numProviders = mProviders.size();
1226         int numConnectedProviders = 0;
1227         for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
1228             if (entry.getValue().getHasEverConnected()) {
1229                 numConnectedProviders++;
1230             }
1231         }
1232         mWifiMetrics.updateSavedPasspointProfilesInfo(mProviders);
1233         mWifiMetrics.updateSavedPasspointProfiles(numProviders, numConnectedProviders);
1234     }
1235 
1236     /**
1237      * Dump the current state of PasspointManager to the provided output stream.
1238      *
1239      * @param pw The output stream to write to
1240      */
dump(PrintWriter pw)1241     public void dump(PrintWriter pw) {
1242         pw.println("Dump of PasspointManager");
1243         pw.println("PasspointManager - Providers Begin ---");
1244         for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
1245             pw.println(entry.getValue());
1246         }
1247         pw.println("PasspointManager - Providers End ---");
1248         pw.println("PasspointManager - Next provider ID to be assigned " + mProviderIndex);
1249         mAnqpCache.dump(pw);
1250         mAnqpRequestManager.dump(pw);
1251     }
1252 
1253     /**
1254      * Add a legacy Passpoint configuration represented by a {@link WifiConfiguration}.
1255      *
1256      * @param wifiConfig {@link WifiConfiguration} representation of the Passpoint configuration
1257      * @return true on success
1258      */
addWifiConfig(WifiConfiguration wifiConfig)1259     private boolean addWifiConfig(WifiConfiguration wifiConfig) {
1260         if (wifiConfig == null) {
1261             return false;
1262         }
1263 
1264         // Convert to PasspointConfiguration
1265         PasspointConfiguration passpointConfig =
1266                 PasspointProvider.convertFromWifiConfig(wifiConfig);
1267         if (passpointConfig == null) {
1268             return false;
1269         }
1270 
1271         // Setup aliases for enterprise certificates and key.
1272         WifiEnterpriseConfig enterpriseConfig = wifiConfig.enterpriseConfig;
1273         String caCertificateAliasSuffix = enterpriseConfig.getCaCertificateAlias();
1274         String clientCertAndKeyAliasSuffix = enterpriseConfig.getClientCertificateAlias();
1275         if (passpointConfig.getCredential().getUserCredential() != null
1276                 && TextUtils.isEmpty(caCertificateAliasSuffix)) {
1277             Log.e(TAG, "Missing CA Certificate for user credential");
1278             return false;
1279         }
1280         if (passpointConfig.getCredential().getCertCredential() != null) {
1281             if (TextUtils.isEmpty(caCertificateAliasSuffix)) {
1282                 Log.e(TAG, "Missing CA certificate for Certificate credential");
1283                 return false;
1284             }
1285             if (TextUtils.isEmpty(clientCertAndKeyAliasSuffix)) {
1286                 Log.e(TAG, "Missing client certificate and key for certificate credential");
1287                 return false;
1288             }
1289         }
1290 
1291         // Note that for legacy configuration, the alias for client private key is the same as the
1292         // alias for the client certificate.
1293         PasspointProvider provider = new PasspointProvider(passpointConfig, mKeyStore,
1294                 mWifiCarrierInfoManager,
1295                 mProviderIndex++, wifiConfig.creatorUid, null, false,
1296                 Arrays.asList(enterpriseConfig.getCaCertificateAlias()),
1297                 enterpriseConfig.getClientCertificateAlias(), null, false, false, mClock);
1298         provider.enableVerboseLogging(mVerboseLoggingEnabled);
1299         mProviders.put(passpointConfig.getUniqueId(), provider);
1300         return true;
1301     }
1302 
1303     /**
1304      * Start the subscription provisioning flow with a provider.
1305      * @param callingUid integer indicating the uid of the caller
1306      * @param provider {@link OsuProvider} the provider to subscribe to
1307      * @param callback {@link IProvisioningCallback} callback to update status to the caller
1308      * @return boolean return value from the provisioning method
1309      */
startSubscriptionProvisioning(int callingUid, OsuProvider provider, IProvisioningCallback callback)1310     public boolean startSubscriptionProvisioning(int callingUid, OsuProvider provider,
1311             IProvisioningCallback callback) {
1312         return mPasspointProvisioner.startSubscriptionProvisioning(callingUid, provider, callback);
1313     }
1314 
1315     /**
1316      * Check if a Passpoint configuration is expired
1317      *
1318      * @param config {@link PasspointConfiguration} Passpoint configuration
1319      * @return True if the configuration is expired, false if not or expiration is unset
1320      */
isExpired(@onNull PasspointConfiguration config)1321     private boolean isExpired(@NonNull PasspointConfiguration config) {
1322         long expirationTime = config.getSubscriptionExpirationTimeMillis();
1323 
1324         if (expirationTime != Long.MIN_VALUE) {
1325             long curTime = System.currentTimeMillis();
1326 
1327             // Check expiration and return true for expired profiles
1328             if (curTime >= expirationTime) {
1329                 Log.d(TAG, "Profile for " + config.getServiceFriendlyName() + " has expired, "
1330                         + "expiration time: " + expirationTime + ", current time: "
1331                         + curTime);
1332                 return true;
1333             }
1334         }
1335         return false;
1336     }
1337 
1338     /**
1339      * Get the filtered ScanResults which could be served by the {@link PasspointConfiguration}.
1340      * @param passpointConfiguration The instance of {@link PasspointConfiguration}
1341      * @param scanResults The list of {@link ScanResult}
1342      * @return The filtered ScanResults
1343      */
1344     @NonNull
getMatchingScanResults( @onNull PasspointConfiguration passpointConfiguration, @NonNull List<ScanResult> scanResults)1345     public List<ScanResult> getMatchingScanResults(
1346             @NonNull PasspointConfiguration passpointConfiguration,
1347             @NonNull List<ScanResult> scanResults) {
1348         PasspointProvider provider = mObjectFactory.makePasspointProvider(passpointConfiguration,
1349                 null, mWifiCarrierInfoManager, 0, 0, null, false, mClock);
1350         List<ScanResult> filteredScanResults = new ArrayList<>();
1351         for (ScanResult scanResult : scanResults) {
1352             PasspointMatch matchInfo = provider.match(getANQPElements(scanResult),
1353                     InformationElementUtil.getRoamingConsortiumIE(scanResult.informationElements),
1354                     scanResult);
1355             if (matchInfo == PasspointMatch.HomeProvider
1356                     || matchInfo == PasspointMatch.RoamingProvider) {
1357                 filteredScanResults.add(scanResult);
1358             }
1359         }
1360 
1361         return filteredScanResults;
1362     }
1363 
1364     /**
1365      * Check if the providers list is empty
1366      *
1367      * @return true if the providers list is empty, false otherwise
1368      */
isProvidersListEmpty()1369     public boolean isProvidersListEmpty() {
1370         return mProviders.isEmpty();
1371     }
1372 
1373     /**
1374      * Clear ANQP requests and flush ANQP Cache (for factory reset)
1375      */
clearAnqpRequestsAndFlushCache()1376     public void clearAnqpRequestsAndFlushCache() {
1377         mAnqpRequestManager.clear();
1378         mAnqpCache.flush();
1379         mProviders.values().stream().forEach(p -> p.clearProviderBlock());
1380     }
1381 
1382     private PKIXParameters mInjectedPKIXParameters;
1383     private boolean mUseInjectedPKIX = false;
1384 
1385 
1386     /**
1387      * Used to speedup unit test.
1388      */
1389     @VisibleForTesting
injectPKIXParameters(PKIXParameters params)1390     public void injectPKIXParameters(PKIXParameters params) {
1391         mInjectedPKIXParameters = params;
1392     }
1393 
1394     /**
1395      * Used to speedup unit test.
1396      */
1397     @VisibleForTesting
setUseInjectedPKIX(boolean value)1398     public void setUseInjectedPKIX(boolean value) {
1399         mUseInjectedPKIX = value;
1400     }
1401 
1402     /**
1403      * Verify that the given certificate is trusted by one of the pre-loaded public CAs in the
1404      * system key store.
1405      *
1406      * @param caCert The CA Certificate to verify
1407      * @throws CertPathValidatorException
1408      * @throws Exception
1409      */
verifyCaCert(X509Certificate caCert)1410     private void verifyCaCert(X509Certificate caCert)
1411             throws GeneralSecurityException, IOException {
1412         CertificateFactory factory = CertificateFactory.getInstance("X.509");
1413         CertPathValidator validator =
1414                 CertPathValidator.getInstance(CertPathValidator.getDefaultType());
1415         CertPath path = factory.generateCertPath(Arrays.asList(caCert));
1416         PKIXParameters params;
1417         if (mUseInjectedPKIX) {
1418             params = mInjectedPKIXParameters;
1419         } else {
1420             KeyStore ks = KeyStore.getInstance("AndroidCAStore");
1421             ks.load(null, null);
1422             params = new PKIXParameters(ks);
1423             params.setRevocationEnabled(false);
1424         }
1425         validator.validate(path, params);
1426     }
1427 
1428     /**
1429      * Request the Venue URL ANQP-element from the AP post connection
1430      *
1431      * @param scanResult Scan result associated to the requested AP
1432      */
requestVenueUrlAnqpElement(@onNull ScanResult scanResult)1433     public void requestVenueUrlAnqpElement(@NonNull ScanResult scanResult) {
1434         long bssid;
1435         try {
1436             bssid = Utils.parseMac(scanResult.BSSID);
1437         } catch (IllegalArgumentException e) {
1438             Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID);
1439             return;
1440         }
1441         InformationElementUtil.Vsa vsa = InformationElementUtil.getHS2VendorSpecificIE(
1442                 scanResult.informationElements);
1443         ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(scanResult.SSID, bssid, scanResult.hessid,
1444                 vsa.anqpDomainID);
1445         // TODO(haishalom@): Should we limit to R3 only? vsa.hsRelease > NetworkDetail.HSRelease.R2
1446         // I am seeing R2's that respond to Venue URL request, so may keep it this way.
1447         // APs that do not support this ANQP request simply ignore it.
1448         mAnqpRequestManager.requestVenueUrlAnqpElement(bssid, anqpKey);
1449     }
1450 
1451     /**
1452      * Get the Venue URL associated to the scan result, matched to the system language. If no
1453      * Venue URL matches the system language, then entry number one is returned, which is considered
1454      * to be the venue's default language.
1455      *
1456      * @param scanResult Scan result
1457      * @return The Venue URL associated to the scan result or null if not found
1458      */
1459     @Nullable
getVenueUrl(@onNull ScanResult scanResult)1460     public URL getVenueUrl(@NonNull ScanResult scanResult) {
1461         long bssid;
1462         try {
1463             bssid = Utils.parseMac(scanResult.BSSID);
1464         } catch (IllegalArgumentException e) {
1465             Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID);
1466             return null;
1467         }
1468         InformationElementUtil.Vsa vsa = InformationElementUtil.getHS2VendorSpecificIE(
1469                 scanResult.informationElements);
1470         ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(scanResult.SSID, bssid, scanResult.hessid,
1471                 vsa.anqpDomainID);
1472         ANQPData anqpEntry = mAnqpCache.getEntry(anqpKey);
1473         if (anqpEntry == null) {
1474             return null;
1475         }
1476         VenueUrlElement venueUrlElement = (VenueUrlElement)
1477                 anqpEntry.getElements().get(Constants.ANQPElementType.ANQPVenueUrl);
1478         if (venueUrlElement == null || venueUrlElement.getVenueUrls().isEmpty()) {
1479             return null; // No Venue URL
1480         }
1481         VenueNameElement venueNameElement = (VenueNameElement)
1482                 anqpEntry.getElements().get(Constants.ANQPElementType.ANQPVenueName);
1483         if (venueNameElement == null
1484                 || venueUrlElement.getVenueUrls().size() != venueNameElement.getNames().size()) {
1485             Log.w(TAG, "Venue name list size mismatches the Venue URL list size");
1486             return null; // No match between Venue names Venue URLs
1487         }
1488 
1489         // Find the Venue URL that matches the system language. Venue URLs are ordered by venue
1490         // names.
1491         Locale locale = Locale.getDefault();
1492         URL venueUrl = null;
1493         int index = 1;
1494         for (I18Name venueName : venueNameElement.getNames()) {
1495             if (venueName.getLanguage().equals(locale.getISO3Language())) {
1496                 venueUrl = venueUrlElement.getVenueUrls().get(index);
1497                 break;
1498             }
1499             index++;
1500         }
1501 
1502         // If no venue URL for the system language is available, use entry number one
1503         if (venueUrl == null) {
1504             venueUrl = venueUrlElement.getVenueUrls().get(1);
1505         }
1506 
1507         if (mVerboseLoggingEnabled) {
1508             Log.d(TAG, "Venue URL to display (language = " + locale.getDisplayLanguage()
1509                     + "): " + (venueUrl != null ? venueUrl : "None"));
1510         }
1511         return venueUrl;
1512     }
1513 
1514     /**
1515      * Handle Deauthentication Imminent WNM-Notification event
1516      *
1517      * @param event Deauthentication Imminent WNM-Notification data
1518      * @param config Configuration of the currently connected network
1519      */
handleDeauthImminentEvent(WnmData event, WifiConfiguration config)1520     public void handleDeauthImminentEvent(WnmData event, WifiConfiguration config) {
1521         if (event == null || config == null) {
1522             return;
1523         }
1524 
1525         blockProvider(config.getProfileKey(), event.getBssid(), event.isEss(),
1526                 event.getDelay());
1527         mWifiMetrics.incrementPasspointDeauthImminentScope(event.isEss());
1528     }
1529 
1530     /**
1531      * Block a specific provider from network selection
1532      *
1533      * @param passpointUniqueId The unique ID of the Passpoint network
1534      * @param bssid BSSID of the AP
1535      * @param isEss Block the ESS or the BSS
1536      * @param delay Delay in seconds
1537      */
blockProvider(String passpointUniqueId, long bssid, boolean isEss, int delay)1538     private void blockProvider(String passpointUniqueId, long bssid, boolean isEss, int delay) {
1539         PasspointProvider provider = mProviders.get(passpointUniqueId);
1540         if (provider != null) {
1541             provider.blockBssOrEss(bssid, isEss, delay);
1542         }
1543     }
1544 
1545     /**
1546      * Store the AnonymousIdentity for passpoint after connection.
1547      */
setAnonymousIdentity(WifiConfiguration configuration)1548     public void setAnonymousIdentity(WifiConfiguration configuration) {
1549         if (!configuration.isPasspoint()) {
1550             return;
1551         }
1552         PasspointProvider provider = mProviders.get(configuration.getProfileKey());
1553         if (provider != null) {
1554             provider.setAnonymousIdentity(configuration.enterpriseConfig.getAnonymousIdentity());
1555             mWifiConfigManager.saveToStore(true);
1556         }
1557     }
1558 
1559     /**
1560      * Resets all sim networks state.
1561      */
resetSimPasspointNetwork()1562     public void resetSimPasspointNetwork() {
1563         mProviders.values().stream().forEach(p -> p.setAnonymousIdentity(null));
1564         mWifiConfigManager.saveToStore(true);
1565     }
1566 
1567     /**
1568      * Handle Terms & Conditions acceptance required WNM-Notification event
1569      *
1570      * @param event Terms & Conditions WNM-Notification data
1571      * @param config Configuration of the currently connected Passpoint network
1572      *
1573      * @return The Terms & conditions URL if it is valid, null otherwise
1574      */
handleTermsAndConditionsEvent(WnmData event, WifiConfiguration config)1575     public URL handleTermsAndConditionsEvent(WnmData event, WifiConfiguration config) {
1576         if (event == null || config == null || !config.isPasspoint()) {
1577             return null;
1578         }
1579         final int oneHourInSeconds = 60 * 60;
1580         final int twentyFourHoursInSeconds = 24 * 60 * 60;
1581         final URL termsAndConditionsUrl;
1582         try {
1583             termsAndConditionsUrl = new URL(event.getUrl());
1584         } catch (java.net.MalformedURLException e) {
1585             Log.e(TAG, "Malformed Terms and Conditions URL: " + event.getUrl()
1586                     + " from BSSID: " + Utils.macToString(event.getBssid()));
1587 
1588             // Block this provider for an hour, this unlikely issue may be resolved shortly
1589             blockProvider(config.getProfileKey(), event.getBssid(), true, oneHourInSeconds);
1590             return null;
1591         }
1592         // Reject URLs that are not HTTPS
1593         if (!TextUtils.equals(termsAndConditionsUrl.getProtocol(), "https")) {
1594             Log.e(TAG, "Non-HTTPS Terms and Conditions URL rejected: " + termsAndConditionsUrl
1595                     + " from BSSID: " + Utils.macToString(event.getBssid()));
1596 
1597             // Block this provider for 24 hours, it is unlikely to be changed
1598             blockProvider(config.getProfileKey(), event.getBssid(), true,
1599                     twentyFourHoursInSeconds);
1600             return null;
1601         }
1602         Log.i(TAG, "Captive network, Terms and Conditions URL: " + termsAndConditionsUrl
1603                 + " from BSSID: " + Utils.macToString(event.getBssid()));
1604         return termsAndConditionsUrl;
1605     }
1606 }
1607