PasspointManager.java revision c56add5be84ea0cf85bc77d5efc0494a68466570
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
17package com.android.server.wifi.hotspot2;
18
19import static android.net.wifi.WifiManager.ACTION_PASSPOINT_DEAUTH_IMMINENT;
20import static android.net.wifi.WifiManager.ACTION_PASSPOINT_ICON;
21import static android.net.wifi.WifiManager.ACTION_PASSPOINT_SUBSCRIPTION_REMEDIATION;
22import static android.net.wifi.WifiManager.EXTRA_BSSID_LONG;
23import static android.net.wifi.WifiManager.EXTRA_DELAY;
24import static android.net.wifi.WifiManager.EXTRA_ESS;
25import static android.net.wifi.WifiManager.EXTRA_FILENAME;
26import static android.net.wifi.WifiManager.EXTRA_ICON;
27import static android.net.wifi.WifiManager.EXTRA_SUBSCRIPTION_REMEDIATION_METHOD;
28import static android.net.wifi.WifiManager.EXTRA_URL;
29
30import android.content.Context;
31import android.content.Intent;
32import android.graphics.drawable.Icon;
33import android.net.wifi.ScanResult;
34import android.net.wifi.WifiConfiguration;
35import android.net.wifi.WifiEnterpriseConfig;
36import android.net.wifi.hotspot2.OsuProvider;
37import android.net.wifi.hotspot2.PasspointConfiguration;
38import android.os.UserHandle;
39import android.text.TextUtils;
40import android.util.Log;
41import android.util.Pair;
42
43import com.android.server.wifi.Clock;
44import com.android.server.wifi.SIMAccessor;
45import com.android.server.wifi.WifiConfigManager;
46import com.android.server.wifi.WifiConfigStore;
47import com.android.server.wifi.WifiKeyStore;
48import com.android.server.wifi.WifiNative;
49import com.android.server.wifi.hotspot2.anqp.ANQPElement;
50import com.android.server.wifi.hotspot2.anqp.Constants;
51import com.android.server.wifi.hotspot2.anqp.HSOsuProvidersElement;
52import com.android.server.wifi.hotspot2.anqp.OsuProviderInfo;
53import com.android.server.wifi.util.InformationElementUtil;
54import com.android.server.wifi.util.ScanResultUtil;
55
56import java.io.PrintWriter;
57import java.util.ArrayList;
58import java.util.HashMap;
59import java.util.List;
60import 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 */
78public 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
126        @Override
127        public void onIconResponse(long bssid, String fileName, byte[] data) {
128            Intent intent = new Intent(ACTION_PASSPOINT_ICON);
129            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
130            intent.putExtra(EXTRA_BSSID_LONG, bssid);
131            intent.putExtra(EXTRA_FILENAME, fileName);
132            if (data != null) {
133                intent.putExtra(EXTRA_ICON, Icon.createWithData(data, 0, data.length));
134            }
135            mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
136                    android.Manifest.permission.ACCESS_WIFI_STATE);
137        }
138
139        @Override
140        public void onWnmFrameReceived(WnmData event) {
141            // %012x HS20-SUBSCRIPTION-REMEDIATION "%u %s", osu_method, url
142            // %012x HS20-DEAUTH-IMMINENT-NOTICE "%u %u %s", code, reauth_delay, url
143            Intent intent;
144            if (event.isDeauthEvent()) {
145                intent = new Intent(ACTION_PASSPOINT_DEAUTH_IMMINENT);
146                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
147                intent.putExtra(EXTRA_BSSID_LONG, event.getBssid());
148                intent.putExtra(EXTRA_URL, event.getUrl());
149                intent.putExtra(EXTRA_ESS, event.isEss());
150                intent.putExtra(EXTRA_DELAY, event.getDelay());
151            } else {
152                intent = new Intent(ACTION_PASSPOINT_SUBSCRIPTION_REMEDIATION);
153                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
154                intent.putExtra(EXTRA_BSSID_LONG, event.getBssid());
155                intent.putExtra(EXTRA_SUBSCRIPTION_REMEDIATION_METHOD, event.getMethod());
156                intent.putExtra(EXTRA_URL, event.getUrl());
157            }
158            mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
159                    android.Manifest.permission.ACCESS_WIFI_STATE);
160        }
161    }
162
163    /**
164     * Data provider for the Passpoint configuration store data {@link PasspointConfigStoreData}.
165     */
166    private class DataSourceHandler implements PasspointConfigStoreData.DataSource {
167        @Override
168        public List<PasspointProvider> getProviders() {
169            List<PasspointProvider> providers = new ArrayList<>();
170            for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
171                providers.add(entry.getValue());
172            }
173            return providers;
174        }
175
176        @Override
177        public void setProviders(List<PasspointProvider> providers) {
178            mProviders.clear();
179            for (PasspointProvider provider : providers) {
180                mProviders.put(provider.getConfig().getHomeSp().getFqdn(), provider);
181            }
182        }
183
184        @Override
185        public long getProviderIndex() {
186            return mProviderIndex;
187        }
188
189        @Override
190        public void setProviderIndex(long providerIndex) {
191            mProviderIndex = providerIndex;
192        }
193    }
194
195    public PasspointManager(Context context, WifiNative wifiNative, WifiKeyStore keyStore,
196            Clock clock, SIMAccessor simAccessor, PasspointObjectFactory objectFactory,
197            WifiConfigManager wifiConfigManager, WifiConfigStore wifiConfigStore) {
198        mHandler = objectFactory.makePasspointEventHandler(wifiNative,
199                new CallbackHandler(context));
200        mKeyStore = keyStore;
201        mSimAccessor = simAccessor;
202        mObjectFactory = objectFactory;
203        mProviders = new HashMap<>();
204        mAnqpCache = objectFactory.makeAnqpCache(clock);
205        mAnqpRequestManager = objectFactory.makeANQPRequestManager(mHandler, clock);
206        mCertVerifier = objectFactory.makeCertificateVerifier();
207        mWifiConfigManager = wifiConfigManager;
208        mProviderIndex = 0;
209        wifiConfigStore.registerStoreData(objectFactory.makePasspointConfigStoreData(
210                mKeyStore, mSimAccessor, new DataSourceHandler()));
211        sPasspointManager = this;
212    }
213
214    /**
215     * Add or update a Passpoint provider with the given configuration.
216     *
217     * Each provider is uniquely identified by its FQDN (Fully Qualified Domain Name).
218     * In the case when there is an existing configuration with the same FQDN
219     * a provider with the new configuration will replace the existing provider.
220     *
221     * @param config Configuration of the Passpoint provider to be added
222     * @return true if provider is added, false otherwise
223     */
224    public boolean addOrUpdateProvider(PasspointConfiguration config, int uid) {
225        if (config == null) {
226            Log.e(TAG, "Configuration not provided");
227            return false;
228        }
229        if (!config.validate()) {
230            Log.e(TAG, "Invalid configuration");
231            return false;
232        }
233
234        // For Hotspot 2.0 Release 1, the CA Certificate must be trusted by one of the pre-loaded
235        // public CAs in the system key store on the device.  Since the provisioning method
236        // for Release 1 is not standardized nor trusted,  this is a reasonable restriction
237        // to improve security.  The presence of UpdateIdentifier is used to differentiate
238        // between R1 and R2 configuration.
239        if (config.getUpdateIdentifier() == Integer.MIN_VALUE
240                && config.getCredential().getCaCertificate() != null) {
241            try {
242                mCertVerifier.verifyCaCert(config.getCredential().getCaCertificate());
243            } catch (Exception e) {
244                Log.e(TAG, "Failed to verify CA certificate: " + e.getMessage());
245                return false;
246            }
247        }
248
249        // Create a provider and install the necessary certificates and keys.
250        PasspointProvider newProvider = mObjectFactory.makePasspointProvider(
251                config, mKeyStore, mSimAccessor, mProviderIndex++, uid);
252
253        if (!newProvider.installCertsAndKeys()) {
254            Log.e(TAG, "Failed to install certificates and keys to keystore");
255            return false;
256        }
257
258        // Remove existing provider with the same FQDN.
259        if (mProviders.containsKey(config.getHomeSp().getFqdn())) {
260            Log.d(TAG, "Replacing configuration for " + config.getHomeSp().getFqdn());
261            mProviders.get(config.getHomeSp().getFqdn()).uninstallCertsAndKeys();
262            mProviders.remove(config.getHomeSp().getFqdn());
263        }
264
265        mProviders.put(config.getHomeSp().getFqdn(), newProvider);
266        mWifiConfigManager.saveToStore(true /* forceWrite */);
267        Log.d(TAG, "Added/updated Passpoint configuration: " + config.getHomeSp().getFqdn()
268                + " by " + uid);
269        return true;
270    }
271
272    /**
273     * Remove a Passpoint provider identified by the given FQDN.
274     *
275     * @param fqdn The FQDN of the provider to remove
276     * @return true if a provider is removed, false otherwise
277     */
278    public boolean removeProvider(String fqdn) {
279        if (!mProviders.containsKey(fqdn)) {
280            Log.e(TAG, "Config doesn't exist");
281            return false;
282        }
283
284        mProviders.get(fqdn).uninstallCertsAndKeys();
285        mProviders.remove(fqdn);
286        mWifiConfigManager.saveToStore(true /* forceWrite */);
287        Log.d(TAG, "Removed Passpoint configuration: " + fqdn);
288        return true;
289    }
290
291    /**
292     * Return the installed Passpoint provider configurations.
293     *
294     * An empty list will be returned when no provider is installed.
295     *
296     * @return A list of {@link PasspointConfiguration}
297     */
298    public List<PasspointConfiguration> getProviderConfigs() {
299        List<PasspointConfiguration> configs = new ArrayList<>();
300        for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
301            configs.add(entry.getValue().getConfig());
302        }
303        return configs;
304    }
305
306    /**
307     * Find the best provider that can provide service through the given AP, which means the
308     * provider contained credential to authenticate with the given AP.
309     *
310     * Here is the current precedence of the matching rule in descending order:
311     * 1. Home Provider
312     * 2. Roaming Provider
313     *
314     * A {code null} will be returned if no matching is found.
315     *
316     * @param scanResult The scan result associated with the AP
317     * @return A pair of {@link PasspointProvider} and match status.
318     */
319    public Pair<PasspointProvider, PasspointMatch> matchProvider(ScanResult scanResult) {
320        // Retrieve the relevant information elements, mainly Roaming Consortium IE and Hotspot 2.0
321        // Vendor Specific IE.
322        InformationElementUtil.RoamingConsortium roamingConsortium =
323                InformationElementUtil.getRoamingConsortiumIE(scanResult.informationElements);
324        InformationElementUtil.Vsa vsa = InformationElementUtil.getHS2VendorSpecificIE(
325                scanResult.informationElements);
326
327        // Lookup ANQP data in the cache.
328        long bssid;
329        try {
330            bssid = Utils.parseMac(scanResult.BSSID);
331        } catch (IllegalArgumentException e) {
332            Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID);
333            return null;
334        }
335        ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(scanResult.SSID, bssid, scanResult.hessid,
336                vsa.anqpDomainID);
337        ANQPData anqpEntry = mAnqpCache.getEntry(anqpKey);
338
339        if (anqpEntry == null) {
340            mAnqpRequestManager.requestANQPElements(bssid, anqpKey,
341                    roamingConsortium.anqpOICount > 0,
342                    vsa.hsRelease  == NetworkDetail.HSRelease.R2);
343            Log.d(TAG, "ANQP entry not found for: " + anqpKey);
344            return null;
345        }
346
347        Pair<PasspointProvider, PasspointMatch> bestMatch = null;
348        for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
349            PasspointProvider provider = entry.getValue();
350            PasspointMatch matchStatus = provider.match(anqpEntry.getElements());
351            if (matchStatus == PasspointMatch.HomeProvider) {
352                bestMatch = Pair.create(provider, matchStatus);
353                break;
354            }
355            if (matchStatus == PasspointMatch.RoamingProvider && bestMatch == null) {
356                bestMatch = Pair.create(provider, matchStatus);
357            }
358        }
359        if (bestMatch != null) {
360            Log.d(TAG, String.format("Matched %s to %s as %s", scanResult.SSID,
361                    bestMatch.first.getConfig().getHomeSp().getFqdn(),
362                    bestMatch.second == PasspointMatch.HomeProvider ? "Home Provider"
363                            : "Roaming Provider"));
364        } else {
365            Log.d(TAG, "Match not found for " + scanResult.SSID);
366        }
367        return bestMatch;
368    }
369
370    /**
371     * Add a legacy Passpoint configuration represented by a {@link WifiConfiguration} to the
372     * current {@link PasspointManager}.
373     *
374     * This will not trigger a config store write, since this will be invoked as part of the
375     * configuration migration, the caller will be responsible for triggering store write
376     * after the migration is completed.
377     *
378     * @param config {@link WifiConfiguration} representation of the Passpoint configuration
379     * @return true on success
380     */
381    public static boolean addLegacyPasspointConfig(WifiConfiguration config) {
382        if (sPasspointManager == null) {
383            Log.e(TAG, "PasspointManager have not been initialized yet");
384            return false;
385        }
386        Log.d(TAG, "Installing legacy Passpoint configuration: " + config.FQDN);
387        return sPasspointManager.addWifiConfig(config);
388    }
389
390    /**
391     * Sweep the ANQP cache to remove expired entries.
392     */
393    public void sweepCache() {
394        mAnqpCache.sweep();
395    }
396
397    /**
398     * Notify the completion of an ANQP request.
399     * TODO(zqiu): currently the notification is done through WifiMonitor,
400     * will no longer be the case once we switch over to use wificond.
401     */
402    public void notifyANQPDone(AnqpEvent anqpEvent) {
403        mHandler.notifyANQPDone(anqpEvent);
404    }
405
406    /**
407     * Notify the completion of an icon request.
408     * TODO(zqiu): currently the notification is done through WifiMonitor,
409     * will no longer be the case once we switch over to use wificond.
410     */
411    public void notifyIconDone(IconEvent iconEvent) {
412        mHandler.notifyIconDone(iconEvent);
413    }
414
415    /**
416     * Notify the reception of a Wireless Network Management (WNM) frame.
417     * TODO(zqiu): currently the notification is done through WifiMonitor,
418     * will no longer be the case once we switch over to use wificond.
419     */
420    public void receivedWnmFrame(WnmData data) {
421        mHandler.notifyWnmFrameReceived(data);
422    }
423
424    /**
425     * Request the specified icon file |fileName| from the specified AP |bssid|.
426     * @return true if the request is sent successfully, false otherwise
427     */
428    public boolean queryPasspointIcon(long bssid, String fileName) {
429        return mHandler.requestIcon(bssid, fileName);
430    }
431
432    /**
433     * Lookup the ANQP elements associated with the given AP from the cache. An empty map
434     * will be returned if no match found in the cache.
435     *
436     * @param scanResult The scan result associated with the AP
437     * @return Map of ANQP elements
438     */
439    public Map<Constants.ANQPElementType, ANQPElement> getANQPElements(ScanResult scanResult) {
440        // Retrieve the Hotspot 2.0 Vendor Specific IE.
441        InformationElementUtil.Vsa vsa =
442                InformationElementUtil.getHS2VendorSpecificIE(scanResult.informationElements);
443
444        // Lookup ANQP data in the cache.
445        long bssid;
446        try {
447            bssid = Utils.parseMac(scanResult.BSSID);
448        } catch (IllegalArgumentException e) {
449            Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID);
450            return new HashMap<Constants.ANQPElementType, ANQPElement>();
451        }
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        if (!scanResult.isPasspointNetwork()) {
476            Log.e(TAG, "Attempt to get matching config for a non-Passpoint AP");
477            return null;
478        }
479        Pair<PasspointProvider, PasspointMatch> matchedProvider = matchProvider(scanResult);
480        if (matchedProvider == null) {
481            return null;
482        }
483        WifiConfiguration config = matchedProvider.first.getWifiConfig();
484        config.SSID = ScanResultUtil.createQuotedSSID(scanResult.SSID);
485        if (matchedProvider.second == PasspointMatch.HomeProvider) {
486            config.isHomeProviderNetwork = true;
487        }
488        return config;
489    }
490
491    /**
492     * Return the list of Hosspot 2.0 OSU (Online Sign-Up) providers associated with the given
493     * AP.
494     *
495     * An empty list will be returned when an invalid scan result is provided or no match is found.
496     *
497     * @param scanResult The scan result of the AP
498     * @return List of {@link OsuProvider}
499     */
500    public List<OsuProvider> getMatchingOsuProviders(ScanResult scanResult) {
501        if (scanResult == null) {
502            Log.e(TAG, "Attempt to retrieve OSU providers for a null ScanResult");
503            return new ArrayList<OsuProvider>();
504        }
505        if (!scanResult.isPasspointNetwork()) {
506            Log.e(TAG, "Attempt to retrieve OSU providers for a non-Passpoint AP");
507            return new ArrayList<OsuProvider>();
508        }
509
510        // Lookup OSU Providers ANQP element.
511        Map<Constants.ANQPElementType, ANQPElement> anqpElements = getANQPElements(scanResult);
512        if (!anqpElements.containsKey(Constants.ANQPElementType.HSOSUProviders)) {
513            return new ArrayList<OsuProvider>();
514        }
515
516        HSOsuProvidersElement element =
517                (HSOsuProvidersElement) anqpElements.get(Constants.ANQPElementType.HSOSUProviders);
518        List<OsuProvider> providers = new ArrayList<>();
519        for (OsuProviderInfo info : element.getProviders()) {
520            // TODO(b/62256482): include icon data once the icon file retrieval and management
521            // support is added.
522            OsuProvider provider = new OsuProvider(element.getOsuSsid(), info.getFriendlyName(),
523                    info.getServiceDescription(), info.getServerUri(),
524                    info.getNetworkAccessIdentifier(), info.getMethodList(), null);
525            providers.add(provider);
526        }
527        return providers;
528    }
529
530    /**
531     * Dump the current state of PasspointManager to the provided output stream.
532     *
533     * @param pw The output stream to write to
534     */
535    public void dump(PrintWriter pw) {
536        pw.println("Dump of PasspointManager");
537        pw.println("PasspointManager - Providers Begin ---");
538        for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
539            pw.println(entry.getValue());
540        }
541        pw.println("PasspointManager - Providers End ---");
542        pw.println("PasspointManager - Next provider ID to be assigned " + mProviderIndex);
543        mAnqpCache.dump(pw);
544    }
545
546    /**
547     * Add a legacy Passpoint configuration represented by a {@link WifiConfiguration}.
548     *
549     * @param wifiConfig {@link WifiConfiguration} representation of the Passpoint configuration
550     * @return true on success
551     */
552    private boolean addWifiConfig(WifiConfiguration wifiConfig) {
553        if (wifiConfig == null) {
554            return false;
555        }
556
557        // Convert to PasspointConfiguration
558        PasspointConfiguration passpointConfig =
559                PasspointProvider.convertFromWifiConfig(wifiConfig);
560        if (passpointConfig == null) {
561            return false;
562        }
563
564        // Setup aliases for enterprise certificates and key.
565        WifiEnterpriseConfig enterpriseConfig = wifiConfig.enterpriseConfig;
566        String caCertificateAliasSuffix = enterpriseConfig.getCaCertificateAlias();
567        String clientCertAndKeyAliasSuffix = enterpriseConfig.getClientCertificateAlias();
568        if (passpointConfig.getCredential().getUserCredential() != null
569                && TextUtils.isEmpty(caCertificateAliasSuffix)) {
570            Log.e(TAG, "Missing CA Certificate for user credential");
571            return false;
572        }
573        if (passpointConfig.getCredential().getCertCredential() != null) {
574            if (TextUtils.isEmpty(caCertificateAliasSuffix)) {
575                Log.e(TAG, "Missing CA certificate for Certificate credential");
576                return false;
577            }
578            if (TextUtils.isEmpty(clientCertAndKeyAliasSuffix)) {
579                Log.e(TAG, "Missing client certificate and key for certificate credential");
580                return false;
581            }
582        }
583
584        // Note that for legacy configuration, the alias for client private key is the same as the
585        // alias for the client certificate.
586        PasspointProvider provider = new PasspointProvider(passpointConfig, mKeyStore,
587                mSimAccessor, mProviderIndex++, wifiConfig.creatorUid,
588                enterpriseConfig.getCaCertificateAlias(),
589                enterpriseConfig.getClientCertificateAlias(),
590                enterpriseConfig.getClientCertificateAlias());
591        mProviders.put(passpointConfig.getHomeSp().getFqdn(), provider);
592        return true;
593    }
594}
595