• Home
  • History
  • Annotate
  • 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.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;
CallbackHandler(Context context)106         CallbackHandler(Context context) {
107             mContext = context;
108         }
109 
110         @Override
onANQPResponse(long bssid, Map<Constants.ANQPElementType, ANQPElement> anqpElements)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
onIconResponse(long bssid, String fileName, byte[] data)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
onWnmFrameReceived(WnmData event)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
getProviders()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
setProviders(List<PasspointProvider> providers)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
getProviderIndex()197         public long getProviderIndex() {
198             return mProviderIndex;
199         }
200 
201         @Override
setProviderIndex(long providerIndex)202         public void setProviderIndex(long providerIndex) {
203             mProviderIndex = providerIndex;
204         }
205     }
206 
PasspointManager(Context context, WifiNative wifiNative, WifiKeyStore keyStore, Clock clock, SIMAccessor simAccessor, PasspointObjectFactory objectFactory, WifiConfigManager wifiConfigManager, WifiConfigStore wifiConfigStore)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      */
addOrUpdateProvider(PasspointConfiguration config, int uid)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      */
removeProvider(String fqdn)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      */
getProviderConfigs()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      */
matchProvider(ScanResult scanResult)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      */
addLegacyPasspointConfig(WifiConfiguration config)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      */
sweepCache()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      */
notifyANQPDone(AnqpEvent anqpEvent)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      */
notifyIconDone(IconEvent iconEvent)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      */
receivedWnmFrame(WnmData data)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      */
queryPasspointIcon(long bssid, String fileName)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      */
getANQPElements(ScanResult scanResult)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      */
getMatchingWifiConfig(ScanResult scanResult)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      */
dump(PrintWriter pw)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      */
addWifiConfig(WifiConfiguration wifiConfig)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