Home | History | Annotate | Download | only in hotspot2
      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.net.wifi.WifiManager.ACTION_PASSPOINT_DEAUTH_IMMINENT;
     20 import static android.net.wifi.WifiManager.ACTION_PASSPOINT_ICON;
     21 import static android.net.wifi.WifiManager.ACTION_PASSPOINT_OSU_PROVIDERS_LIST;
     22 import static android.net.wifi.WifiManager.ACTION_PASSPOINT_SUBSCRIPTION_REMEDIATION;
     23 import static android.net.wifi.WifiManager.EXTRA_ANQP_ELEMENT_DATA;
     24 import static android.net.wifi.WifiManager.EXTRA_BSSID_LONG;
     25 import static android.net.wifi.WifiManager.EXTRA_DELAY;
     26 import static android.net.wifi.WifiManager.EXTRA_ESS;
     27 import static android.net.wifi.WifiManager.EXTRA_FILENAME;
     28 import static android.net.wifi.WifiManager.EXTRA_ICON;
     29 import static android.net.wifi.WifiManager.EXTRA_SUBSCRIPTION_REMEDIATION_METHOD;
     30 import static android.net.wifi.WifiManager.EXTRA_URL;
     31 
     32 import android.content.Context;
     33 import android.content.Intent;
     34 import android.graphics.drawable.Icon;
     35 import android.net.wifi.ScanResult;
     36 import android.net.wifi.WifiConfiguration;
     37 import android.net.wifi.WifiEnterpriseConfig;
     38 import android.net.wifi.hotspot2.PasspointConfiguration;
     39 import android.os.UserHandle;
     40 import android.text.TextUtils;
     41 import android.util.Log;
     42 import android.util.Pair;
     43 
     44 import com.android.server.wifi.Clock;
     45 import com.android.server.wifi.SIMAccessor;
     46 import com.android.server.wifi.WifiConfigManager;
     47 import com.android.server.wifi.WifiConfigStore;
     48 import com.android.server.wifi.WifiKeyStore;
     49 import com.android.server.wifi.WifiNative;
     50 import com.android.server.wifi.hotspot2.anqp.ANQPElement;
     51 import com.android.server.wifi.hotspot2.anqp.Constants;
     52 import com.android.server.wifi.hotspot2.anqp.RawByteElement;
     53 import com.android.server.wifi.util.InformationElementUtil;
     54 import com.android.server.wifi.util.ScanResultUtil;
     55 
     56 import java.io.PrintWriter;
     57 import java.util.ArrayList;
     58 import java.util.HashMap;
     59 import java.util.List;
     60 import java.util.Map;
     61 
     62 /**
     63  * This class provides the APIs to manage Passpoint provider configurations.
     64  * It deals with the following:
     65  * - Maintaining a list of configured Passpoint providers for provider matching.
     66  * - Persisting the providers configurations to store when required.
     67  * - matching Passpoint providers based on the scan results
     68  * - Supporting WifiManager Public API calls:
     69  *   > addOrUpdatePasspointConfiguration()
     70  *   > removePasspointConfiguration()
     71  *   > getPasspointConfigurations()
     72  *
     73  * The provider matching requires obtaining additional information from the AP (ANQP elements).
     74  * The ANQP elements will be cached using {@link AnqpCache} to avoid unnecessary requests.
     75  *
     76  * NOTE: These API's are not thread safe and should only be used from WifiStateMachine thread.
     77  */
     78 public class PasspointManager {
     79     private static final String TAG = "PasspointManager";
     80 
     81     /**
     82      * Handle for the current {@link PasspointManager} instance.  This is needed to avoid
     83      * circular dependency with the WifiConfigManger, it will be used for adding the
     84      * legacy Passpoint configurations.
     85      *
     86      * This can be eliminated once we can remove the dependency for WifiConfigManager (for
     87      * triggering config store write) from this class.
     88      */
     89     private static PasspointManager sPasspointManager;
     90 
     91     private final PasspointEventHandler mHandler;
     92     private final SIMAccessor mSimAccessor;
     93     private final WifiKeyStore mKeyStore;
     94     private final PasspointObjectFactory mObjectFactory;
     95     private final Map<String, PasspointProvider> mProviders;
     96     private final AnqpCache mAnqpCache;
     97     private final ANQPRequestManager mAnqpRequestManager;
     98     private final WifiConfigManager mWifiConfigManager;
     99     private final CertificateVerifier mCertVerifier;
    100 
    101     // Counter used for assigning unique identifier to each provider.
    102     private long mProviderIndex;
    103 
    104     private class CallbackHandler implements PasspointEventHandler.Callbacks {
    105         private final Context mContext;
    106         CallbackHandler(Context context) {
    107             mContext = context;
    108         }
    109 
    110         @Override
    111         public void onANQPResponse(long bssid,
    112                 Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
    113             // Notify request manager for the completion of a request.
    114             ANQPNetworkKey anqpKey =
    115                     mAnqpRequestManager.onRequestCompleted(bssid, anqpElements != null);
    116             if (anqpElements == null || anqpKey == null) {
    117                 // Query failed or the request wasn't originated from us (not tracked by the
    118                 // request manager). Nothing to be done.
    119                 return;
    120             }
    121 
    122             // Add new entry to the cache.
    123             mAnqpCache.addEntry(anqpKey, anqpElements);
    124 
    125             // Broadcast OSU providers info.
    126             if (anqpElements.containsKey(Constants.ANQPElementType.HSOSUProviders)) {
    127                 RawByteElement osuProviders = (RawByteElement) anqpElements.get(
    128                         Constants.ANQPElementType.HSOSUProviders);
    129                 Intent intent = new Intent(ACTION_PASSPOINT_OSU_PROVIDERS_LIST);
    130                 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
    131                 intent.putExtra(EXTRA_BSSID_LONG, bssid);
    132                 intent.putExtra(EXTRA_ANQP_ELEMENT_DATA, osuProviders.getPayload());
    133                 mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
    134                         android.Manifest.permission.ACCESS_WIFI_STATE);
    135             }
    136         }
    137 
    138         @Override
    139         public void onIconResponse(long bssid, String fileName, byte[] data) {
    140             Intent intent = new Intent(ACTION_PASSPOINT_ICON);
    141             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
    142             intent.putExtra(EXTRA_BSSID_LONG, bssid);
    143             intent.putExtra(EXTRA_FILENAME, fileName);
    144             if (data != null) {
    145                 intent.putExtra(EXTRA_ICON, Icon.createWithData(data, 0, data.length));
    146             }
    147             mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
    148                     android.Manifest.permission.ACCESS_WIFI_STATE);
    149         }
    150 
    151         @Override
    152         public void onWnmFrameReceived(WnmData event) {
    153             // %012x HS20-SUBSCRIPTION-REMEDIATION "%u %s", osu_method, url
    154             // %012x HS20-DEAUTH-IMMINENT-NOTICE "%u %u %s", code, reauth_delay, url
    155             Intent intent;
    156             if (event.isDeauthEvent()) {
    157                 intent = new Intent(ACTION_PASSPOINT_DEAUTH_IMMINENT);
    158                 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
    159                 intent.putExtra(EXTRA_BSSID_LONG, event.getBssid());
    160                 intent.putExtra(EXTRA_URL, event.getUrl());
    161                 intent.putExtra(EXTRA_ESS, event.isEss());
    162                 intent.putExtra(EXTRA_DELAY, event.getDelay());
    163             } else {
    164                 intent = new Intent(ACTION_PASSPOINT_SUBSCRIPTION_REMEDIATION);
    165                 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
    166                 intent.putExtra(EXTRA_BSSID_LONG, event.getBssid());
    167                 intent.putExtra(EXTRA_SUBSCRIPTION_REMEDIATION_METHOD, event.getMethod());
    168                 intent.putExtra(EXTRA_URL, event.getUrl());
    169             }
    170             mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
    171                     android.Manifest.permission.ACCESS_WIFI_STATE);
    172         }
    173     }
    174 
    175     /**
    176      * Data provider for the Passpoint configuration store data {@link PasspointConfigStoreData}.
    177      */
    178     private class DataSourceHandler implements PasspointConfigStoreData.DataSource {
    179         @Override
    180         public List<PasspointProvider> getProviders() {
    181             List<PasspointProvider> providers = new ArrayList<>();
    182             for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
    183                 providers.add(entry.getValue());
    184             }
    185             return providers;
    186         }
    187 
    188         @Override
    189         public void setProviders(List<PasspointProvider> providers) {
    190             mProviders.clear();
    191             for (PasspointProvider provider : providers) {
    192                 mProviders.put(provider.getConfig().getHomeSp().getFqdn(), provider);
    193             }
    194         }
    195 
    196         @Override
    197         public long getProviderIndex() {
    198             return mProviderIndex;
    199         }
    200 
    201         @Override
    202         public void setProviderIndex(long providerIndex) {
    203             mProviderIndex = providerIndex;
    204         }
    205     }
    206 
    207     public PasspointManager(Context context, WifiNative wifiNative, WifiKeyStore keyStore,
    208             Clock clock, SIMAccessor simAccessor, PasspointObjectFactory objectFactory,
    209             WifiConfigManager wifiConfigManager, WifiConfigStore wifiConfigStore) {
    210         mHandler = objectFactory.makePasspointEventHandler(wifiNative,
    211                 new CallbackHandler(context));
    212         mKeyStore = keyStore;
    213         mSimAccessor = simAccessor;
    214         mObjectFactory = objectFactory;
    215         mProviders = new HashMap<>();
    216         mAnqpCache = objectFactory.makeAnqpCache(clock);
    217         mAnqpRequestManager = objectFactory.makeANQPRequestManager(mHandler, clock);
    218         mCertVerifier = objectFactory.makeCertificateVerifier();
    219         mWifiConfigManager = wifiConfigManager;
    220         mProviderIndex = 0;
    221         wifiConfigStore.registerStoreData(objectFactory.makePasspointConfigStoreData(
    222                 mKeyStore, mSimAccessor, new DataSourceHandler()));
    223         sPasspointManager = this;
    224     }
    225 
    226     /**
    227      * Add or update a Passpoint provider with the given configuration.
    228      *
    229      * Each provider is uniquely identified by its FQDN (Fully Qualified Domain Name).
    230      * In the case when there is an existing configuration with the same FQDN
    231      * a provider with the new configuration will replace the existing provider.
    232      *
    233      * @param config Configuration of the Passpoint provider to be added
    234      * @return true if provider is added, false otherwise
    235      */
    236     public boolean addOrUpdateProvider(PasspointConfiguration config, int uid) {
    237         if (config == null) {
    238             Log.e(TAG, "Configuration not provided");
    239             return false;
    240         }
    241         if (!config.validate()) {
    242             Log.e(TAG, "Invalid configuration");
    243             return false;
    244         }
    245 
    246         // For Hotspot 2.0 Release 1, the CA Certificate must be trusted by one of the pre-loaded
    247         // public CAs in the system key store on the device.  Since the provisioning method
    248         // for Release 1 is not standardized nor trusted,  this is a reasonable restriction
    249         // to improve security.  The presence of UpdateIdentifier is used to differentiate
    250         // between R1 and R2 configuration.
    251         if (config.getUpdateIdentifier() == Integer.MIN_VALUE
    252                 && config.getCredential().getCaCertificate() != null) {
    253             try {
    254                 mCertVerifier.verifyCaCert(config.getCredential().getCaCertificate());
    255             } catch (Exception e) {
    256                 Log.e(TAG, "Failed to verify CA certificate: " + e.getMessage());
    257                 return false;
    258             }
    259         }
    260 
    261         // Create a provider and install the necessary certificates and keys.
    262         PasspointProvider newProvider = mObjectFactory.makePasspointProvider(
    263                 config, mKeyStore, mSimAccessor, mProviderIndex++, uid);
    264 
    265         if (!newProvider.installCertsAndKeys()) {
    266             Log.e(TAG, "Failed to install certificates and keys to keystore");
    267             return false;
    268         }
    269 
    270         // Remove existing provider with the same FQDN.
    271         if (mProviders.containsKey(config.getHomeSp().getFqdn())) {
    272             Log.d(TAG, "Replacing configuration for " + config.getHomeSp().getFqdn());
    273             mProviders.get(config.getHomeSp().getFqdn()).uninstallCertsAndKeys();
    274             mProviders.remove(config.getHomeSp().getFqdn());
    275         }
    276 
    277         mProviders.put(config.getHomeSp().getFqdn(), newProvider);
    278         mWifiConfigManager.saveToStore(true /* forceWrite */);
    279         Log.d(TAG, "Added/updated Passpoint configuration: " + config.getHomeSp().getFqdn()
    280                 + " by " + uid);
    281         return true;
    282     }
    283 
    284     /**
    285      * Remove a Passpoint provider identified by the given FQDN.
    286      *
    287      * @param fqdn The FQDN of the provider to remove
    288      * @return true if a provider is removed, false otherwise
    289      */
    290     public boolean removeProvider(String fqdn) {
    291         if (!mProviders.containsKey(fqdn)) {
    292             Log.e(TAG, "Config doesn't exist");
    293             return false;
    294         }
    295 
    296         mProviders.get(fqdn).uninstallCertsAndKeys();
    297         mProviders.remove(fqdn);
    298         mWifiConfigManager.saveToStore(true /* forceWrite */);
    299         Log.d(TAG, "Removed Passpoint configuration: " + fqdn);
    300         return true;
    301     }
    302 
    303     /**
    304      * Return the installed Passpoint provider configurations.
    305      *
    306      * An empty list will be returned when no provider is installed.
    307      *
    308      * @return A list of {@link PasspointConfiguration}
    309      */
    310     public List<PasspointConfiguration> getProviderConfigs() {
    311         List<PasspointConfiguration> configs = new ArrayList<>();
    312         for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
    313             configs.add(entry.getValue().getConfig());
    314         }
    315         return configs;
    316     }
    317 
    318     /**
    319      * Find the best provider that can provide service through the given AP, which means the
    320      * provider contained credential to authenticate with the given AP.
    321      *
    322      * Here is the current precedence of the matching rule in descending order:
    323      * 1. Home Provider
    324      * 2. Roaming Provider
    325      *
    326      * A {code null} will be returned if no matching is found.
    327      *
    328      * @param scanResult The scan result associated with the AP
    329      * @return A pair of {@link PasspointProvider} and match status.
    330      */
    331     public Pair<PasspointProvider, PasspointMatch> matchProvider(ScanResult scanResult) {
    332         // Retrieve the relevant information elements, mainly Roaming Consortium IE and Hotspot 2.0
    333         // Vendor Specific IE.
    334         InformationElementUtil.RoamingConsortium roamingConsortium =
    335                 InformationElementUtil.getRoamingConsortiumIE(scanResult.informationElements);
    336         InformationElementUtil.Vsa vsa = InformationElementUtil.getHS2VendorSpecificIE(
    337                 scanResult.informationElements);
    338 
    339         // Lookup ANQP data in the cache.
    340         long bssid = Utils.parseMac(scanResult.BSSID);
    341         ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(scanResult.SSID, bssid, scanResult.hessid,
    342                 vsa.anqpDomainID);
    343         ANQPData anqpEntry = mAnqpCache.getEntry(anqpKey);
    344 
    345         if (anqpEntry == null) {
    346             mAnqpRequestManager.requestANQPElements(bssid, anqpKey,
    347                     roamingConsortium.anqpOICount > 0,
    348                     vsa.hsRelease  == NetworkDetail.HSRelease.R2);
    349             Log.d(TAG, "ANQP entry not found for: " + anqpKey);
    350             return null;
    351         }
    352 
    353         Pair<PasspointProvider, PasspointMatch> bestMatch = null;
    354         for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
    355             PasspointProvider provider = entry.getValue();
    356             PasspointMatch matchStatus = provider.match(anqpEntry.getElements());
    357             if (matchStatus == PasspointMatch.HomeProvider) {
    358                 bestMatch = Pair.create(provider, matchStatus);
    359                 break;
    360             }
    361             if (matchStatus == PasspointMatch.RoamingProvider && bestMatch == null) {
    362                 bestMatch = Pair.create(provider, matchStatus);
    363             }
    364         }
    365         if (bestMatch != null) {
    366             Log.d(TAG, String.format("Matched %s to %s as %s", scanResult.SSID,
    367                     bestMatch.first.getConfig().getHomeSp().getFqdn(),
    368                     bestMatch.second == PasspointMatch.HomeProvider ? "Home Provider"
    369                             : "Roaming Provider"));
    370         } else {
    371             Log.d(TAG, "Match not found for " + scanResult.SSID);
    372         }
    373         return bestMatch;
    374     }
    375 
    376     /**
    377      * Add a legacy Passpoint configuration represented by a {@link WifiConfiguration} to the
    378      * current {@link PasspointManager}.
    379      *
    380      * This will not trigger a config store write, since this will be invoked as part of the
    381      * configuration migration, the caller will be responsible for triggering store write
    382      * after the migration is completed.
    383      *
    384      * @param config {@link WifiConfiguration} representation of the Passpoint configuration
    385      * @return true on success
    386      */
    387     public static boolean addLegacyPasspointConfig(WifiConfiguration config) {
    388         if (sPasspointManager == null) {
    389             Log.e(TAG, "PasspointManager have not been initialized yet");
    390             return false;
    391         }
    392         Log.d(TAG, "Installing legacy Passpoint configuration: " + config.FQDN);
    393         return sPasspointManager.addWifiConfig(config);
    394     }
    395 
    396     /**
    397      * Sweep the ANQP cache to remove expired entries.
    398      */
    399     public void sweepCache() {
    400         mAnqpCache.sweep();
    401     }
    402 
    403     /**
    404      * Notify the completion of an ANQP request.
    405      * TODO(zqiu): currently the notification is done through WifiMonitor,
    406      * will no longer be the case once we switch over to use wificond.
    407      */
    408     public void notifyANQPDone(AnqpEvent anqpEvent) {
    409         mHandler.notifyANQPDone(anqpEvent);
    410     }
    411 
    412     /**
    413      * Notify the completion of an icon request.
    414      * TODO(zqiu): currently the notification is done through WifiMonitor,
    415      * will no longer be the case once we switch over to use wificond.
    416      */
    417     public void notifyIconDone(IconEvent iconEvent) {
    418         mHandler.notifyIconDone(iconEvent);
    419     }
    420 
    421     /**
    422      * Notify the reception of a Wireless Network Management (WNM) frame.
    423      * TODO(zqiu): currently the notification is done through WifiMonitor,
    424      * will no longer be the case once we switch over to use wificond.
    425      */
    426     public void receivedWnmFrame(WnmData data) {
    427         mHandler.notifyWnmFrameReceived(data);
    428     }
    429 
    430     /**
    431      * Request the specified icon file |fileName| from the specified AP |bssid|.
    432      * @return true if the request is sent successfully, false otherwise
    433      */
    434     public boolean queryPasspointIcon(long bssid, String fileName) {
    435         return mHandler.requestIcon(bssid, fileName);
    436     }
    437 
    438     /**
    439      * Lookup the ANQP elements associated with the given AP from the cache. An empty map
    440      * will be returned if no match found in the cache.
    441      *
    442      * @param scanResult The scan result associated with the AP
    443      * @return Map of ANQP elements
    444      */
    445     public Map<Constants.ANQPElementType, ANQPElement> getANQPElements(ScanResult scanResult) {
    446         // Retrieve the Hotspot 2.0 Vendor Specific IE.
    447         InformationElementUtil.Vsa vsa =
    448                 InformationElementUtil.getHS2VendorSpecificIE(scanResult.informationElements);
    449 
    450         // Lookup ANQP data in the cache.
    451         long bssid = Utils.parseMac(scanResult.BSSID);
    452         ANQPData anqpEntry = mAnqpCache.getEntry(ANQPNetworkKey.buildKey(
    453                 scanResult.SSID, bssid, scanResult.hessid, vsa.anqpDomainID));
    454         if (anqpEntry != null) {
    455             return anqpEntry.getElements();
    456         }
    457         return new HashMap<Constants.ANQPElementType, ANQPElement>();
    458     }
    459 
    460     /**
    461      * Match the given WiFi AP to an installed Passpoint provider.  A {@link WifiConfiguration}
    462      * will be generated and returned if a match is found.  The returned {@link WifiConfiguration}
    463      * will contained all the necessary credentials for connecting to the given WiFi AP.
    464      *
    465      * A {code null} will be returned if no matching provider is found.
    466      *
    467      * @param scanResult The scan result of the given AP
    468      * @return {@link WifiConfiguration}
    469      */
    470     public WifiConfiguration getMatchingWifiConfig(ScanResult scanResult) {
    471         if (scanResult == null) {
    472             Log.e(TAG, "Attempt to get matching config for a null ScanResult");
    473             return null;
    474         }
    475         Pair<PasspointProvider, PasspointMatch> matchedProvider = matchProvider(scanResult);
    476         if (matchedProvider == null) {
    477             return null;
    478         }
    479         WifiConfiguration config = matchedProvider.first.getWifiConfig();
    480         config.SSID = ScanResultUtil.createQuotedSSID(scanResult.SSID);
    481         if (matchedProvider.second == PasspointMatch.HomeProvider) {
    482             config.isHomeProviderNetwork = true;
    483         }
    484         return config;
    485     }
    486 
    487     /**
    488      * Dump the current state of PasspointManager to the provided output stream.
    489      *
    490      * @param pw The output stream to write to
    491      */
    492     public void dump(PrintWriter pw) {
    493         pw.println("Dump of PasspointManager");
    494         pw.println("PasspointManager - Providers Begin ---");
    495         for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
    496             pw.println(entry.getValue());
    497         }
    498         pw.println("PasspointManager - Providers End ---");
    499         pw.println("PasspointManager - Next provider ID to be assigned " + mProviderIndex);
    500         mAnqpCache.dump(pw);
    501     }
    502 
    503     /**
    504      * Add a legacy Passpoint configuration represented by a {@link WifiConfiguration}.
    505      *
    506      * @param wifiConfig {@link WifiConfiguration} representation of the Passpoint configuration
    507      * @return true on success
    508      */
    509     private boolean addWifiConfig(WifiConfiguration wifiConfig) {
    510         if (wifiConfig == null) {
    511             return false;
    512         }
    513 
    514         // Convert to PasspointConfiguration
    515         PasspointConfiguration passpointConfig =
    516                 PasspointProvider.convertFromWifiConfig(wifiConfig);
    517         if (passpointConfig == null) {
    518             return false;
    519         }
    520 
    521         // Setup aliases for enterprise certificates and key.
    522         WifiEnterpriseConfig enterpriseConfig = wifiConfig.enterpriseConfig;
    523         String caCertificateAliasSuffix = enterpriseConfig.getCaCertificateAlias();
    524         String clientCertAndKeyAliasSuffix = enterpriseConfig.getClientCertificateAlias();
    525         if (passpointConfig.getCredential().getUserCredential() != null
    526                 && TextUtils.isEmpty(caCertificateAliasSuffix)) {
    527             Log.e(TAG, "Missing CA Certificate for user credential");
    528             return false;
    529         }
    530         if (passpointConfig.getCredential().getCertCredential() != null) {
    531             if (TextUtils.isEmpty(caCertificateAliasSuffix)) {
    532                 Log.e(TAG, "Missing CA certificate for Certificate credential");
    533                 return false;
    534             }
    535             if (TextUtils.isEmpty(clientCertAndKeyAliasSuffix)) {
    536                 Log.e(TAG, "Missing client certificate and key for certificate credential");
    537                 return false;
    538             }
    539         }
    540 
    541         // Note that for legacy configuration, the alias for client private key is the same as the
    542         // alias for the client certificate.
    543         PasspointProvider provider = new PasspointProvider(passpointConfig, mKeyStore,
    544                 mSimAccessor, mProviderIndex++, wifiConfig.creatorUid,
    545                 enterpriseConfig.getCaCertificateAlias(),
    546                 enterpriseConfig.getClientCertificateAlias(),
    547                 enterpriseConfig.getClientCertificateAlias());
    548         mProviders.put(passpointConfig.getHomeSp().getFqdn(), provider);
    549         return true;
    550     }
    551 }
    552