• 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.net.wifi.WifiConfiguration.MeteredOverride;
20 
21 import static com.android.server.wifi.MboOceConstants.DEFAULT_BLOCKLIST_DURATION_MS;
22 
23 import android.annotation.Nullable;
24 import android.net.wifi.EAPConstants;
25 import android.net.wifi.ScanResult;
26 import android.net.wifi.SecurityParams;
27 import android.net.wifi.WifiConfiguration;
28 import android.net.wifi.WifiEnterpriseConfig;
29 import android.net.wifi.hotspot2.PasspointConfiguration;
30 import android.net.wifi.hotspot2.pps.Credential;
31 import android.net.wifi.hotspot2.pps.Credential.SimCredential;
32 import android.net.wifi.hotspot2.pps.Credential.UserCredential;
33 import android.net.wifi.hotspot2.pps.HomeSp;
34 import android.telephony.SubscriptionManager;
35 import android.telephony.TelephonyManager;
36 import android.text.TextUtils;
37 import android.util.Base64;
38 import android.util.Log;
39 import android.util.Pair;
40 
41 import com.android.modules.utils.build.SdkLevel;
42 import com.android.server.wifi.Clock;
43 import com.android.server.wifi.IMSIParameter;
44 import com.android.server.wifi.WifiCarrierInfoManager;
45 import com.android.server.wifi.WifiKeyStore;
46 import com.android.server.wifi.hotspot2.anqp.ANQPElement;
47 import com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType;
48 import com.android.server.wifi.hotspot2.anqp.DomainNameElement;
49 import com.android.server.wifi.hotspot2.anqp.NAIRealmElement;
50 import com.android.server.wifi.hotspot2.anqp.RoamingConsortiumElement;
51 import com.android.server.wifi.hotspot2.anqp.ThreeGPPNetworkElement;
52 import com.android.server.wifi.hotspot2.anqp.eap.AuthParam;
53 import com.android.server.wifi.hotspot2.anqp.eap.NonEAPInnerAuth;
54 import com.android.server.wifi.util.ArrayUtils;
55 import com.android.server.wifi.util.InformationElementUtil.RoamingConsortium;
56 
57 import java.nio.charset.StandardCharsets;
58 import java.security.MessageDigest;
59 import java.security.NoSuchAlgorithmException;
60 import java.security.PrivateKey;
61 import java.security.cert.Certificate;
62 import java.security.cert.CertificateEncodingException;
63 import java.security.cert.X509Certificate;
64 import java.util.ArrayList;
65 import java.util.Arrays;
66 import java.util.List;
67 import java.util.Map;
68 import java.util.Objects;
69 
70 /**
71  * Abstraction for Passpoint service provider.  This class contains the both static
72  * Passpoint configuration data and the runtime data (e.g. blacklisted SSIDs, statistics).
73  */
74 public class PasspointProvider {
75     private static final String TAG = "PasspointProvider";
76 
77     /**
78      * Used as part of alias string for certificates and keys.  The alias string is in the format
79      * of: [KEY_TYPE]_HS2_[ProviderID]
80      * For example: "CACERT_HS2_0", "USRCERT_HS2_0", "USRPKEY_HS2_0", "CACERT_HS2_REMEDIATION_0"
81      */
82     private static final String ALIAS_HS_TYPE = "HS2_";
83     private static final String ALIAS_ALIAS_REMEDIATION_TYPE = "REMEDIATION_";
84 
85     private static final String SYSTEM_CA_STORE_PATH = "/system/etc/security/cacerts";
86 
87     private final PasspointConfiguration mConfig;
88     private final WifiKeyStore mKeyStore;
89 
90     /**
91      * Aliases for the private keys and certificates installed in the keystore.  Each alias
92      * is a suffix of the actual certificate or key name installed in the keystore.  The
93      * certificate or key name in the keystore is consist of |Type|_|alias|.
94      * This will be consistent with the usage of the term "alias" in {@link WifiEnterpriseConfig}.
95      */
96     private List<String> mCaCertificateAliases;
97     private String mClientPrivateKeyAndCertificateAlias;
98     private String mRemediationCaCertificateAlias;
99 
100     private final long mProviderId;
101     private final int mCreatorUid;
102     private final String mPackageName;
103 
104     private final IMSIParameter mImsiParameter;
105 
106     private final int mEAPMethodID;
107     private final AuthParam mAuthParam;
108     private final WifiCarrierInfoManager mWifiCarrierInfoManager;
109 
110     private int mBestGuessCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
111     private boolean mHasEverConnected;
112     private boolean mIsShared;
113     private boolean mIsFromSuggestion;
114     private boolean mIsTrusted;
115     private boolean mVerboseLoggingEnabled;
116 
117     private final Clock mClock;
118     private long mReauthDelay = 0;
119     private List<String> mBlockedBssids = new ArrayList<>();
120     private String mAnonymousIdentity = null;
121     private String mConnectChoice = null;
122     private int mConnectChoiceRssi = 0;
123 
PasspointProvider(PasspointConfiguration config, WifiKeyStore keyStore, WifiCarrierInfoManager wifiCarrierInfoManager, long providerId, int creatorUid, String packageName, boolean isFromSuggestion, Clock clock)124     public PasspointProvider(PasspointConfiguration config, WifiKeyStore keyStore,
125             WifiCarrierInfoManager wifiCarrierInfoManager, long providerId, int creatorUid,
126             String packageName, boolean isFromSuggestion, Clock clock) {
127         this(config, keyStore, wifiCarrierInfoManager, providerId, creatorUid, packageName,
128                 isFromSuggestion, null, null, null, false, false, clock);
129     }
130 
PasspointProvider(PasspointConfiguration config, WifiKeyStore keyStore, WifiCarrierInfoManager wifiCarrierInfoManager, long providerId, int creatorUid, String packageName, boolean isFromSuggestion, List<String> caCertificateAliases, String clientPrivateKeyAndCertificateAlias, String remediationCaCertificateAlias, boolean hasEverConnected, boolean isShared, Clock clock)131     public PasspointProvider(PasspointConfiguration config, WifiKeyStore keyStore,
132             WifiCarrierInfoManager wifiCarrierInfoManager, long providerId, int creatorUid,
133             String packageName, boolean isFromSuggestion, List<String> caCertificateAliases,
134             String clientPrivateKeyAndCertificateAlias, String remediationCaCertificateAlias,
135             boolean hasEverConnected, boolean isShared, Clock clock) {
136         // Maintain a copy of the configuration to avoid it being updated by others.
137         mConfig = new PasspointConfiguration(config);
138         mKeyStore = keyStore;
139         mProviderId = providerId;
140         mCreatorUid = creatorUid;
141         mPackageName = packageName;
142         mCaCertificateAliases = caCertificateAliases;
143         mClientPrivateKeyAndCertificateAlias = clientPrivateKeyAndCertificateAlias;
144         mRemediationCaCertificateAlias = remediationCaCertificateAlias;
145         mHasEverConnected = hasEverConnected;
146         mIsShared = isShared;
147         mIsFromSuggestion = isFromSuggestion;
148         mWifiCarrierInfoManager = wifiCarrierInfoManager;
149         mIsTrusted = true;
150         mClock = clock;
151 
152         // Setup EAP method and authentication parameter based on the credential.
153         if (mConfig.getCredential().getUserCredential() != null) {
154             mEAPMethodID = EAPConstants.EAP_TTLS;
155             mAuthParam = new NonEAPInnerAuth(NonEAPInnerAuth.getAuthTypeID(
156                     mConfig.getCredential().getUserCredential().getNonEapInnerMethod()));
157             mImsiParameter = null;
158         } else if (mConfig.getCredential().getCertCredential() != null) {
159             mEAPMethodID = EAPConstants.EAP_TLS;
160             mAuthParam = null;
161             mImsiParameter = null;
162         } else {
163             mEAPMethodID = mConfig.getCredential().getSimCredential().getEapType();
164             mAuthParam = null;
165             mImsiParameter = IMSIParameter.build(
166                     mConfig.getCredential().getSimCredential().getImsi());
167         }
168     }
169 
170     /**
171      * Set passpoint network trusted or not.
172      * Default is true. Only allows to change when it is from suggestion.
173      */
setTrusted(boolean trusted)174     public void setTrusted(boolean trusted) {
175         if (!mIsFromSuggestion) {
176             Log.e(TAG, "setTrusted can only be called for suggestion passpoint network");
177             return;
178         }
179         mIsTrusted = trusted;
180     }
181 
isTrusted()182     public boolean isTrusted() {
183         return mIsTrusted;
184     }
185 
186     /**
187      * Set Anonymous Identity for passpoint network.
188      */
setAnonymousIdentity(String anonymousIdentity)189     public void setAnonymousIdentity(String anonymousIdentity) {
190         mAnonymousIdentity = anonymousIdentity;
191     }
192 
getAnonymousIdentity()193     public String getAnonymousIdentity() {
194         return mAnonymousIdentity;
195     }
196 
getConfig()197     public PasspointConfiguration getConfig() {
198         // Return a copy of the configuration to avoid it being updated by others.
199         return new PasspointConfiguration(mConfig);
200     }
201 
getCaCertificateAliases()202     public List<String> getCaCertificateAliases() {
203         return mCaCertificateAliases;
204     }
205 
getClientPrivateKeyAndCertificateAlias()206     public String getClientPrivateKeyAndCertificateAlias() {
207         return mClientPrivateKeyAndCertificateAlias;
208     }
209 
getRemediationCaCertificateAlias()210     public String getRemediationCaCertificateAlias() {
211         return mRemediationCaCertificateAlias;
212     }
213 
getProviderId()214     public long getProviderId() {
215         return mProviderId;
216     }
217 
getCreatorUid()218     public int getCreatorUid() {
219         return mCreatorUid;
220     }
221 
222     @Nullable
getPackageName()223     public String getPackageName() {
224         return mPackageName;
225     }
226 
getHasEverConnected()227     public boolean getHasEverConnected() {
228         return mHasEverConnected;
229     }
230 
setHasEverConnected(boolean hasEverConnected)231     public void setHasEverConnected(boolean hasEverConnected) {
232         mHasEverConnected = hasEverConnected;
233     }
234 
isFromSuggestion()235     public boolean isFromSuggestion() {
236         return mIsFromSuggestion;
237     }
238 
239     /**
240      * Enable/disable the auto-join configuration of the corresponding passpoint configuration.
241      *
242      * @return true if the setting has changed
243      */
setAutojoinEnabled(boolean autoJoinEnabled)244     public boolean setAutojoinEnabled(boolean autoJoinEnabled) {
245         boolean changed = mConfig.isAutojoinEnabled() != autoJoinEnabled;
246         mConfig.setAutojoinEnabled(autoJoinEnabled);
247         return changed;
248     }
249 
isAutojoinEnabled()250     public boolean isAutojoinEnabled() {
251         return mConfig.isAutojoinEnabled();
252     }
253 
254     /**
255      * Enable/disable mac randomization for this passpoint profile.
256      *
257      * @return true if the setting has changed
258      */
setMacRandomizationEnabled(boolean enabled)259     public boolean setMacRandomizationEnabled(boolean enabled) {
260         boolean changed = mConfig.isMacRandomizationEnabled() != enabled;
261         mConfig.setMacRandomizationEnabled(enabled);
262         return changed;
263     }
264 
265     /**
266      * Get whether mac randomization is enabled for this passpoint profile.
267      */
isMacRandomizationEnabled()268     public boolean isMacRandomizationEnabled() {
269         return mConfig.isMacRandomizationEnabled();
270     }
271 
272     /**
273      * Get the metered override for this passpoint profile.
274      *
275      * @return true if the setting has changed
276      */
setMeteredOverride(@eteredOverride int meteredOverride)277     public boolean setMeteredOverride(@MeteredOverride int meteredOverride) {
278         boolean changed = mConfig.getMeteredOverride() != meteredOverride;
279         mConfig.setMeteredOverride(meteredOverride);
280         return changed;
281     }
282 
283     /**
284      * Install certificates and key based on current configuration.
285      * Note: the certificates and keys in the configuration will get cleared once
286      * they're installed in the keystore.
287      *
288      * @return true on success
289      */
installCertsAndKeys()290     public boolean installCertsAndKeys() {
291         // Install CA certificate.
292         X509Certificate[] x509Certificates = mConfig.getCredential().getCaCertificates();
293         if (x509Certificates != null) {
294             mCaCertificateAliases = new ArrayList<>();
295             for (int i = 0; i < x509Certificates.length; i++) {
296                 String alias = String.format("%s%s_%d", ALIAS_HS_TYPE, mProviderId, i);
297                 if (!mKeyStore.putCaCertInKeyStore(alias, x509Certificates[i])) {
298                     Log.e(TAG, "Failed to install CA Certificate " + alias);
299                     uninstallCertsAndKeys();
300                     return false;
301                 } else {
302                     mCaCertificateAliases.add(alias);
303                 }
304             }
305         }
306 
307         // Install the client private key & certificate.
308         if (mConfig.getCredential().getClientPrivateKey() != null
309                 && mConfig.getCredential().getClientCertificateChain() != null) {
310             String keyName = ALIAS_HS_TYPE + mProviderId;
311             PrivateKey clientKey = mConfig.getCredential().getClientPrivateKey();
312             X509Certificate clientCert = getClientCertificate(
313                     mConfig.getCredential().getClientCertificateChain(),
314                     mConfig.getCredential().getCertCredential().getCertSha256Fingerprint());
315             if (clientCert == null) {
316                 Log.e(TAG, "Failed to locate client certificate");
317                 uninstallCertsAndKeys();
318                 return false;
319             }
320             if (!mKeyStore.putUserPrivKeyAndCertsInKeyStore(
321                     keyName, clientKey, new Certificate[] {clientCert})) {
322                 Log.e(TAG, "Failed to install client private key or certificate");
323                 uninstallCertsAndKeys();
324                 return false;
325             }
326             mClientPrivateKeyAndCertificateAlias = keyName;
327         }
328 
329         if (mConfig.getSubscriptionUpdate() != null) {
330             X509Certificate certificate = mConfig.getSubscriptionUpdate().getCaCertificate();
331             if (certificate == null) {
332                 Log.e(TAG, "Failed to locate CA certificate for remediation");
333                 uninstallCertsAndKeys();
334                 return false;
335             }
336             String certName = ALIAS_HS_TYPE + ALIAS_ALIAS_REMEDIATION_TYPE + mProviderId;
337             if (!mKeyStore.putCaCertInKeyStore(certName, certificate)) {
338                 Log.e(TAG, "Failed to install CA certificate for remediation");
339                 uninstallCertsAndKeys();
340                 return false;
341             }
342             mRemediationCaCertificateAlias = certName;
343         }
344 
345         // Clear the keys and certificates in the configuration.
346         mConfig.getCredential().setCaCertificates(null);
347         mConfig.getCredential().setClientPrivateKey(null);
348         mConfig.getCredential().setClientCertificateChain(null);
349         if (mConfig.getSubscriptionUpdate() != null) {
350             mConfig.getSubscriptionUpdate().setCaCertificate(null);
351         }
352         return true;
353     }
354 
355     /**
356      * Remove any installed certificates and key.
357      */
uninstallCertsAndKeys()358     public void uninstallCertsAndKeys() {
359         if (mCaCertificateAliases != null) {
360             for (String certificateAlias : mCaCertificateAliases) {
361                 if (!mKeyStore.removeEntryFromKeyStore(certificateAlias)) {
362                     Log.e(TAG, "Failed to remove entry: " + certificateAlias);
363                 }
364             }
365             mCaCertificateAliases = null;
366         }
367         if (mClientPrivateKeyAndCertificateAlias != null) {
368             if (!mKeyStore.removeEntryFromKeyStore(mClientPrivateKeyAndCertificateAlias)) {
369                 Log.e(TAG, "Failed to remove entry: " + mClientPrivateKeyAndCertificateAlias);
370             }
371             mClientPrivateKeyAndCertificateAlias = null;
372         }
373         if (mRemediationCaCertificateAlias != null) {
374             if (!mKeyStore.removeEntryFromKeyStore(mRemediationCaCertificateAlias)) {
375                 Log.e(TAG, "Failed to remove entry: " + mRemediationCaCertificateAlias);
376             }
377             mRemediationCaCertificateAlias = null;
378         }
379     }
380 
381     /**
382      * Try to update the carrier ID according to the IMSI parameter of passpoint configuration.
383      *
384      * @return true if the carrier ID is updated, otherwise false.
385      */
tryUpdateCarrierId()386     public boolean tryUpdateCarrierId() {
387         return mWifiCarrierInfoManager.tryUpdateCarrierIdForPasspoint(mConfig);
388     }
389 
getMatchingSimImsi()390     private @Nullable String getMatchingSimImsi() {
391         String matchingSIMImsi = null;
392         if (mConfig.getSubscriptionId() != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
393             matchingSIMImsi = mWifiCarrierInfoManager
394                     .getMatchingImsiBySubId(mConfig.getSubscriptionId());
395         } else if (mConfig.getCarrierId() != TelephonyManager.UNKNOWN_CARRIER_ID) {
396             matchingSIMImsi = mWifiCarrierInfoManager.getMatchingImsiBySubId(
397                     mWifiCarrierInfoManager.getMatchingSubId(mConfig.getCarrierId()));
398         } else {
399             // Get the IMSI and carrier ID of SIM card which match with the IMSI prefix from
400             // passpoint profile
401             Pair<String, Integer> imsiCarrierIdPair = mWifiCarrierInfoManager
402                     .getMatchingImsiCarrierId(mConfig.getCredential().getSimCredential().getImsi());
403             if (imsiCarrierIdPair != null) {
404                 matchingSIMImsi = imsiCarrierIdPair.first;
405                 mBestGuessCarrierId = imsiCarrierIdPair.second;
406             }
407         }
408 
409         return matchingSIMImsi;
410     }
411 
412     /**
413      * Return the matching status with the given AP, based on the ANQP elements from the AP.
414      *
415      * @param anqpElements ANQP elements from the AP
416      * @param roamingConsortiumFromAp Roaming Consortium information element from the AP
417      * @param scanResult Latest Scan result
418      * @return {@link PasspointMatch}
419      */
match(Map<ANQPElementType, ANQPElement> anqpElements, RoamingConsortium roamingConsortiumFromAp, ScanResult scanResult)420     public PasspointMatch match(Map<ANQPElementType, ANQPElement> anqpElements,
421             RoamingConsortium roamingConsortiumFromAp, ScanResult scanResult) {
422         if (isProviderBlocked(scanResult)) {
423             if (mVerboseLoggingEnabled) {
424                 Log.d(TAG, "Provider " + mConfig.getServiceFriendlyName()
425                         + " is blocked because reauthentication delay duration is still in"
426                         + " progess");
427             }
428             return PasspointMatch.None;
429         }
430 
431         // If the profile requires a SIM credential, make sure that the installed SIM matches
432         String matchingSimImsi = null;
433         if (mConfig.getCredential().getSimCredential() != null) {
434             matchingSimImsi = getMatchingSimImsi();
435             if (TextUtils.isEmpty(matchingSimImsi)) {
436                 if (mVerboseLoggingEnabled) {
437                     Log.d(TAG, "No SIM card with IMSI "
438                             + mConfig.getCredential().getSimCredential().getImsi()
439                             + " is installed, final match: " + PasspointMatch.None);
440                 }
441                 return PasspointMatch.None;
442             }
443         }
444 
445         // Match FQDN for Home provider or RCOI(s) for Roaming provider
446         // For SIM credential, the FQDN is in the format of wlan.mnc*.mcc*.3gppnetwork.org
447         PasspointMatch providerMatch = matchFqdnAndRcoi(anqpElements, roamingConsortiumFromAp,
448                 matchingSimImsi);
449 
450         // 3GPP Network matching
451         if (providerMatch == PasspointMatch.None && ANQPMatcher.matchThreeGPPNetwork(
452                 (ThreeGPPNetworkElement) anqpElements.get(ANQPElementType.ANQP3GPPNetwork),
453                 mImsiParameter, matchingSimImsi)) {
454             if (mVerboseLoggingEnabled) {
455                 Log.d(TAG, "Final RoamingProvider match with "
456                         + anqpElements.get(ANQPElementType.ANQP3GPPNetwork));
457             }
458             return PasspointMatch.RoamingProvider;
459         }
460 
461         // Perform NAI Realm matching
462         boolean realmMatch = ANQPMatcher.matchNAIRealm(
463                 (NAIRealmElement) anqpElements.get(ANQPElementType.ANQPNAIRealm),
464                 mConfig.getCredential().getRealm());
465 
466         // In case of no realm match, return provider match as is.
467         if (!realmMatch) {
468             if (mVerboseLoggingEnabled) {
469                 Log.d(TAG, "No NAI realm match, final match: " + providerMatch);
470             }
471             return providerMatch;
472         }
473 
474         if (mVerboseLoggingEnabled) {
475             Log.d(TAG, "NAI realm match with " + mConfig.getCredential().getRealm());
476         }
477 
478         // Promote the provider match to RoamingProvider if provider match is not found, but NAI
479         // realm is matched.
480         if (providerMatch == PasspointMatch.None) {
481             providerMatch = PasspointMatch.RoamingProvider;
482         }
483 
484         if (mVerboseLoggingEnabled) {
485             Log.d(TAG, "Final match: " + providerMatch);
486         }
487         return providerMatch;
488     }
489 
490     /**
491      * Generate a WifiConfiguration based on the provider's configuration.  The generated
492      * WifiConfiguration will include all the necessary credentials for network connection except
493      * the SSID, which should be added by the caller when the config is being used for network
494      * connection.
495      *
496      * @return {@link WifiConfiguration}
497      */
getWifiConfig()498     public WifiConfiguration getWifiConfig() {
499         WifiConfiguration wifiConfig = new WifiConfiguration();
500 
501         List<SecurityParams> paramsList = Arrays.asList(
502                 SecurityParams.createSecurityParamsBySecurityType(
503                         WifiConfiguration.SECURITY_TYPE_PASSPOINT_R1_R2),
504                 SecurityParams.createSecurityParamsBySecurityType(
505                         WifiConfiguration.SECURITY_TYPE_PASSPOINT_R3));
506         wifiConfig.setSecurityParams(paramsList);
507 
508         wifiConfig.FQDN = mConfig.getHomeSp().getFqdn();
509         wifiConfig.setPasspointUniqueId(mConfig.getUniqueId());
510         if (mConfig.getHomeSp().getRoamingConsortiumOis() != null) {
511             wifiConfig.roamingConsortiumIds = Arrays.copyOf(
512                     mConfig.getHomeSp().getRoamingConsortiumOis(),
513                     mConfig.getHomeSp().getRoamingConsortiumOis().length);
514         }
515         if (mConfig.getUpdateIdentifier() != Integer.MIN_VALUE) {
516             // R2 profile, it needs to set updateIdentifier HS2.0 Indication element as PPS MO
517             // ID in Association Request.
518             wifiConfig.updateIdentifier = Integer.toString(mConfig.getUpdateIdentifier());
519             if (isMeteredNetwork(mConfig)) {
520                 wifiConfig.meteredOverride = WifiConfiguration.METERED_OVERRIDE_METERED;
521             }
522         }
523         wifiConfig.providerFriendlyName = mConfig.getHomeSp().getFriendlyName();
524         int carrierId = mConfig.getCarrierId();
525         if (carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
526             carrierId = mBestGuessCarrierId;
527         }
528         wifiConfig.carrierId = carrierId;
529         wifiConfig.subscriptionId =
530                 mConfig.getSubscriptionId() == SubscriptionManager.INVALID_SUBSCRIPTION_ID
531                         ? mWifiCarrierInfoManager.getMatchingSubId(carrierId)
532                         : mConfig.getSubscriptionId();
533         wifiConfig.carrierMerged = mConfig.isCarrierMerged();
534         wifiConfig.oemPaid = mConfig.isOemPaid();
535         wifiConfig.oemPrivate = mConfig.isOemPrivate();
536 
537         WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
538         enterpriseConfig.setRealm(mConfig.getCredential().getRealm());
539         enterpriseConfig.setDomainSuffixMatch(mConfig.getHomeSp().getFqdn());
540         if (mConfig.getCredential().getUserCredential() != null) {
541             buildEnterpriseConfigForUserCredential(enterpriseConfig,
542                     mConfig.getCredential().getUserCredential());
543             setAnonymousIdentityToNaiRealm(enterpriseConfig, mConfig.getCredential().getRealm());
544         } else if (mConfig.getCredential().getCertCredential() != null) {
545             buildEnterpriseConfigForCertCredential(enterpriseConfig);
546             setAnonymousIdentityToNaiRealm(enterpriseConfig, mConfig.getCredential().getRealm());
547         } else {
548             buildEnterpriseConfigForSimCredential(enterpriseConfig,
549                     mConfig.getCredential().getSimCredential());
550             enterpriseConfig.setAnonymousIdentity(mAnonymousIdentity);
551         }
552         // If AAA server trusted names are specified, use it to replace HOME SP FQDN
553         // and use system CA regardless of provisioned CA certificate.
554         if (!ArrayUtils.isEmpty(mConfig.getAaaServerTrustedNames())) {
555             enterpriseConfig.setDomainSuffixMatch(
556                     String.join(";", mConfig.getAaaServerTrustedNames()));
557             enterpriseConfig.setCaPath(SYSTEM_CA_STORE_PATH);
558         }
559         if (SdkLevel.isAtLeastS()) {
560             enterpriseConfig.setDecoratedIdentityPrefix(mConfig.getDecoratedIdentityPrefix());
561         }
562         wifiConfig.enterpriseConfig = enterpriseConfig;
563         // PPS MO Credential/CheckAAAServerCertStatus node contains a flag which indicates
564         // if the mobile device needs to check the AAA server certificate's revocation status
565         // during EAP authentication.
566         if (mConfig.getCredential().getCheckAaaServerCertStatus()) {
567             // Check server certificate using OCSP (Online Certificate Status Protocol).
568             wifiConfig.enterpriseConfig.setOcsp(WifiEnterpriseConfig.OCSP_REQUIRE_CERT_STATUS);
569         }
570         wifiConfig.allowAutojoin = isAutojoinEnabled();
571         wifiConfig.shared = mIsShared;
572         wifiConfig.fromWifiNetworkSuggestion = mIsFromSuggestion;
573         wifiConfig.ephemeral = mIsFromSuggestion;
574         wifiConfig.creatorName = mPackageName;
575         wifiConfig.creatorUid = mCreatorUid;
576         wifiConfig.trusted = mIsTrusted;
577         if (mConfig.isMacRandomizationEnabled()) {
578             if (mConfig.isEnhancedMacRandomizationEnabled()) {
579                 wifiConfig.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_NON_PERSISTENT;
580             } else {
581                 wifiConfig.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_PERSISTENT;
582             }
583         } else {
584             wifiConfig.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_NONE;
585         }
586         wifiConfig.meteredOverride = mConfig.getMeteredOverride();
587         wifiConfig.getNetworkSelectionStatus().setConnectChoice(mConnectChoice);
588         wifiConfig.getNetworkSelectionStatus().setConnectChoiceRssi(mConnectChoiceRssi);
589 
590         return wifiConfig;
591     }
592 
593     /**
594      * @return true if provider is backed by a SIM credential.
595      */
isSimCredential()596     public boolean isSimCredential() {
597         return mConfig.getCredential().getSimCredential() != null;
598     }
599 
600     /**
601      * Convert a legacy {@link WifiConfiguration} representation of a Passpoint configuration to
602      * a {@link PasspointConfiguration}.  This is used for migrating legacy Passpoint
603      * configuration (release N and older).
604      *
605      * @param wifiConfig The {@link WifiConfiguration} to convert
606      * @return {@link PasspointConfiguration}
607      */
convertFromWifiConfig(WifiConfiguration wifiConfig)608     public static PasspointConfiguration convertFromWifiConfig(WifiConfiguration wifiConfig) {
609         PasspointConfiguration passpointConfig = new PasspointConfiguration();
610 
611         // Setup HomeSP.
612         HomeSp homeSp = new HomeSp();
613         if (TextUtils.isEmpty(wifiConfig.FQDN)) {
614             Log.e(TAG, "Missing FQDN");
615             return null;
616         }
617         homeSp.setFqdn(wifiConfig.FQDN);
618         homeSp.setFriendlyName(wifiConfig.providerFriendlyName);
619         if (wifiConfig.roamingConsortiumIds != null) {
620             homeSp.setRoamingConsortiumOis(Arrays.copyOf(
621                     wifiConfig.roamingConsortiumIds, wifiConfig.roamingConsortiumIds.length));
622         }
623         passpointConfig.setHomeSp(homeSp);
624         passpointConfig.setCarrierId(wifiConfig.carrierId);
625 
626         // Setup Credential.
627         Credential credential = new Credential();
628         credential.setRealm(wifiConfig.enterpriseConfig.getRealm());
629         switch (wifiConfig.enterpriseConfig.getEapMethod()) {
630             case WifiEnterpriseConfig.Eap.TTLS:
631                 credential.setUserCredential(buildUserCredentialFromEnterpriseConfig(
632                         wifiConfig.enterpriseConfig));
633                 break;
634             case WifiEnterpriseConfig.Eap.TLS:
635                 Credential.CertificateCredential certCred = new Credential.CertificateCredential();
636                 certCred.setCertType(Credential.CertificateCredential.CERT_TYPE_X509V3);
637                 credential.setCertCredential(certCred);
638                 break;
639             case WifiEnterpriseConfig.Eap.SIM:
640                 credential.setSimCredential(buildSimCredentialFromEnterpriseConfig(
641                         EAPConstants.EAP_SIM, wifiConfig.enterpriseConfig));
642                 break;
643             case WifiEnterpriseConfig.Eap.AKA:
644                 credential.setSimCredential(buildSimCredentialFromEnterpriseConfig(
645                         EAPConstants.EAP_AKA, wifiConfig.enterpriseConfig));
646                 break;
647             case WifiEnterpriseConfig.Eap.AKA_PRIME:
648                 credential.setSimCredential(buildSimCredentialFromEnterpriseConfig(
649                         EAPConstants.EAP_AKA_PRIME, wifiConfig.enterpriseConfig));
650                 break;
651             default:
652                 Log.e(TAG, "Unsupported EAP method: "
653                         + wifiConfig.enterpriseConfig.getEapMethod());
654                 return null;
655         }
656         if (credential.getUserCredential() == null && credential.getCertCredential() == null
657                 && credential.getSimCredential() == null) {
658             Log.e(TAG, "Missing credential");
659             return null;
660         }
661         passpointConfig.setCredential(credential);
662 
663         return passpointConfig;
664     }
665 
666     @Override
equals(Object thatObject)667     public boolean equals(Object thatObject) {
668         if (this == thatObject) {
669             return true;
670         }
671         if (!(thatObject instanceof PasspointProvider)) {
672             return false;
673         }
674         PasspointProvider that = (PasspointProvider) thatObject;
675         return mProviderId == that.mProviderId
676                 && (mCaCertificateAliases == null ? that.mCaCertificateAliases == null
677                 : mCaCertificateAliases.equals(that.mCaCertificateAliases))
678                 && TextUtils.equals(mClientPrivateKeyAndCertificateAlias,
679                 that.mClientPrivateKeyAndCertificateAlias)
680                 && (mConfig == null ? that.mConfig == null : mConfig.equals(that.mConfig))
681                 && TextUtils.equals(mRemediationCaCertificateAlias,
682                 that.mRemediationCaCertificateAlias);
683     }
684 
685     @Override
hashCode()686     public int hashCode() {
687         return Objects.hash(mProviderId, mCaCertificateAliases,
688                 mClientPrivateKeyAndCertificateAlias, mConfig, mRemediationCaCertificateAlias);
689     }
690 
691     @Override
toString()692     public String toString() {
693         StringBuilder builder = new StringBuilder();
694         builder.append("ProviderId: ").append(mProviderId).append("\n");
695         builder.append("CreatorUID: ").append(mCreatorUid).append("\n");
696         builder.append("Best guess Carrier ID: ").append(mBestGuessCarrierId).append("\n");
697         builder.append("Ever connected: ").append(mHasEverConnected).append("\n");
698         builder.append("Shared: ").append(mIsShared).append("\n");
699         builder.append("Suggestion: ").append(mIsFromSuggestion).append("\n");
700         builder.append("Trusted: ").append(mIsTrusted).append("\n");
701         builder.append("UserConnectChoice: ").append(mConnectChoice).append("\n");
702         if (mReauthDelay != 0 && mClock.getElapsedSinceBootMillis() < mReauthDelay) {
703             builder.append("Reauth delay remaining (seconds): ")
704                     .append((mReauthDelay - mClock.getElapsedSinceBootMillis()) / 1000)
705                     .append("\n");
706             if (mBlockedBssids.isEmpty()) {
707                 builder.append("ESS is blocked").append("\n");
708             } else {
709                 builder.append("List of blocked BSSIDs:").append("\n");
710                 for (String bssid : mBlockedBssids) {
711                     builder.append(bssid).append("\n");
712                 }
713             }
714         } else {
715             builder.append("Provider is not blocked").append("\n");
716         }
717 
718         if (mPackageName != null) {
719             builder.append("PackageName: ").append(mPackageName).append("\n");
720         }
721         builder.append("Configuration Begin ---\n");
722         builder.append(mConfig);
723         builder.append("Configuration End ---\n");
724         builder.append("WifiConfiguration Begin ---\n");
725         builder.append(getWifiConfig());
726         builder.append("WifiConfiguration End ---\n");
727         return builder.toString();
728     }
729 
730     /**
731      * Retrieve the client certificate from the certificates chain.  The certificate
732      * with the matching SHA256 digest is the client certificate.
733      *
734      * @param certChain The client certificates chain
735      * @param expectedSha256Fingerprint The expected SHA256 digest of the client certificate
736      * @return {@link java.security.cert.X509Certificate}
737      */
getClientCertificate(X509Certificate[] certChain, byte[] expectedSha256Fingerprint)738     private static X509Certificate getClientCertificate(X509Certificate[] certChain,
739             byte[] expectedSha256Fingerprint) {
740         if (certChain == null) {
741             return null;
742         }
743         try {
744             MessageDigest digester = MessageDigest.getInstance("SHA-256");
745             for (X509Certificate certificate : certChain) {
746                 digester.reset();
747                 byte[] fingerprint = digester.digest(certificate.getEncoded());
748                 if (Arrays.equals(expectedSha256Fingerprint, fingerprint)) {
749                     return certificate;
750                 }
751             }
752         } catch (CertificateEncodingException | NoSuchAlgorithmException e) {
753             return null;
754         }
755 
756         return null;
757     }
758 
759     /**
760      * Determines the Passpoint network is a metered network.
761      *
762      * Expiration date -> non-metered
763      * Data limit -> metered
764      * Time usage limit -> metered
765      * @param passpointConfig instance of {@link PasspointConfiguration}
766      * @return {@code true} if the network is a metered network, {@code false} otherwise.
767      */
isMeteredNetwork(PasspointConfiguration passpointConfig)768     private boolean isMeteredNetwork(PasspointConfiguration passpointConfig) {
769         if (passpointConfig == null) return false;
770 
771         // If DataLimit is zero, there is unlimited data usage for the account.
772         // If TimeLimit is zero, there is unlimited time usage for the account.
773         return passpointConfig.getUsageLimitDataLimit() > 0
774                 || passpointConfig.getUsageLimitTimeLimitInMinutes() > 0;
775     }
776 
777     /**
778      * Match given OIs to the Roaming Consortium OIs
779      *
780      * @param providerOis Provider OIs to match against
781      * @param roamingConsortiumElement RCOIs in the ANQP element
782      * @param roamingConsortiumFromAp RCOIs in the AP scan results
783      * @param matchAll Indicates if all providerOis must match the RCOIs elements
784      * @return {@code true} if there is a match, {@code false} otherwise.
785      */
matchOis(long[] providerOis, RoamingConsortiumElement roamingConsortiumElement, RoamingConsortium roamingConsortiumFromAp, boolean matchAll)786     private boolean matchOis(long[] providerOis,
787             RoamingConsortiumElement roamingConsortiumElement,
788             RoamingConsortium roamingConsortiumFromAp,
789             boolean matchAll) {
790 
791 
792         // ANQP Roaming Consortium OI matching.
793         if (ANQPMatcher.matchRoamingConsortium(roamingConsortiumElement, providerOis, matchAll)) {
794             if (mVerboseLoggingEnabled) {
795                 Log.e(TAG, "ANQP RCOI match " + roamingConsortiumElement);
796             }
797             return true;
798         }
799 
800         // AP Roaming Consortium OI matching.
801         long[] apRoamingConsortiums = roamingConsortiumFromAp.getRoamingConsortiums();
802         if (apRoamingConsortiums == null || providerOis == null) {
803             return false;
804         }
805         // Roaming Consortium OI information element matching.
806         for (long apOi: apRoamingConsortiums) {
807             boolean matched = false;
808             for (long providerOi: providerOis) {
809                 if (apOi == providerOi) {
810                     if (mVerboseLoggingEnabled) {
811                         Log.e(TAG, "AP RCOI match: " + apOi);
812                     }
813                     if (!matchAll) {
814                         return true;
815                     } else {
816                         matched = true;
817                         break;
818                     }
819                 }
820             }
821             if (matchAll && !matched) {
822                 return false;
823             }
824         }
825         return matchAll;
826     }
827 
828     /**
829      * Perform a provider match based on the given ANQP elements for FQDN and RCOI
830      *
831      * @param anqpElements List of ANQP elements
832      * @param roamingConsortiumFromAp Roaming Consortium information element from the AP
833      * @param matchingSIMImsi Installed SIM IMSI that matches the SIM credential ANQP element
834      * @return {@link PasspointMatch}
835      */
matchFqdnAndRcoi(Map<ANQPElementType, ANQPElement> anqpElements, RoamingConsortium roamingConsortiumFromAp, String matchingSIMImsi)836     private PasspointMatch matchFqdnAndRcoi(Map<ANQPElementType, ANQPElement> anqpElements,
837             RoamingConsortium roamingConsortiumFromAp, String matchingSIMImsi) {
838         // Domain name matching.
839         if (ANQPMatcher.matchDomainName(
840                 (DomainNameElement) anqpElements.get(ANQPElementType.ANQPDomName),
841                 mConfig.getHomeSp().getFqdn(), mImsiParameter, matchingSIMImsi)) {
842             if (mVerboseLoggingEnabled) {
843                 Log.d(TAG, "Domain name " + mConfig.getHomeSp().getFqdn()
844                         + " match: HomeProvider");
845             }
846             return PasspointMatch.HomeProvider;
847         }
848 
849         // Other Home Partners matching.
850         if (mConfig.getHomeSp().getOtherHomePartners() != null) {
851             for (String otherHomePartner : mConfig.getHomeSp().getOtherHomePartners()) {
852                 if (ANQPMatcher.matchDomainName(
853                         (DomainNameElement) anqpElements.get(ANQPElementType.ANQPDomName),
854                         otherHomePartner, null, null)) {
855                     if (mVerboseLoggingEnabled) {
856                         Log.d(TAG, "Other Home Partner " + otherHomePartner
857                                 + " match: HomeProvider");
858                     }
859                     return PasspointMatch.HomeProvider;
860                 }
861             }
862         }
863 
864         // HomeOI matching
865         if (mConfig.getHomeSp().getMatchAllOis() != null) {
866             // Ensure that every HomeOI whose corresponding HomeOIRequired value is true shall match
867             // an OI in the Roaming Consortium advertised by the hotspot operator.
868             if (matchOis(mConfig.getHomeSp().getMatchAllOis(), (RoamingConsortiumElement)
869                             anqpElements.get(ANQPElementType.ANQPRoamingConsortium),
870                     roamingConsortiumFromAp, true)) {
871                 if (mVerboseLoggingEnabled) {
872                     Log.e(TAG, "All HomeOI RCOI match: HomeProvider");
873                 }
874                 return PasspointMatch.HomeProvider;
875             }
876         } else if (mConfig.getHomeSp().getMatchAnyOis() != null) {
877             // Ensure that any HomeOI whose corresponding HomeOIRequired value is false shall match
878             // an OI in the Roaming Consortium advertised by the hotspot operator.
879             if (matchOis(mConfig.getHomeSp().getMatchAnyOis(), (RoamingConsortiumElement)
880                             anqpElements.get(ANQPElementType.ANQPRoamingConsortium),
881                     roamingConsortiumFromAp, false)) {
882                 if (mVerboseLoggingEnabled) {
883                     Log.e(TAG, "Any HomeOI RCOI match: HomeProvider");
884                 }
885                 return PasspointMatch.HomeProvider;
886             }
887         }
888 
889         // Roaming Consortium OI matching.
890         if (matchOis(mConfig.getHomeSp().getRoamingConsortiumOis(), (RoamingConsortiumElement)
891                 anqpElements.get(ANQPElementType.ANQPRoamingConsortium),
892                 roamingConsortiumFromAp, false)) {
893             if (mVerboseLoggingEnabled) {
894                 Log.e(TAG, "ANQP RCOI match: RoamingProvider");
895             }
896             return PasspointMatch.RoamingProvider;
897         }
898         if (mVerboseLoggingEnabled) {
899             Log.e(TAG, "No domain name or RCOI match");
900         }
901         return PasspointMatch.None;
902     }
903 
904     /**
905      * Fill in WifiEnterpriseConfig with information from an user credential.
906      *
907      * @param config Instance of {@link WifiEnterpriseConfig}
908      * @param credential Instance of {@link UserCredential}
909      */
buildEnterpriseConfigForUserCredential(WifiEnterpriseConfig config, Credential.UserCredential credential)910     private void buildEnterpriseConfigForUserCredential(WifiEnterpriseConfig config,
911             Credential.UserCredential credential) {
912         String password;
913         try {
914             byte[] pwOctets = Base64.decode(credential.getPassword(), Base64.DEFAULT);
915             password = new String(pwOctets, StandardCharsets.UTF_8);
916         } catch (IllegalArgumentException e) {
917             Log.w(TAG, "Failed to decode password");
918             password = credential.getPassword();
919         }
920         config.setEapMethod(WifiEnterpriseConfig.Eap.TTLS);
921         config.setIdentity(credential.getUsername());
922         config.setPassword(password);
923         if (!ArrayUtils.isEmpty(mCaCertificateAliases)) {
924             config.setCaCertificateAliases(mCaCertificateAliases.toArray(new String[0]));
925         } else {
926             config.setCaPath(SYSTEM_CA_STORE_PATH);
927         }
928         int phase2Method = WifiEnterpriseConfig.Phase2.NONE;
929         switch (credential.getNonEapInnerMethod()) {
930             case Credential.UserCredential.AUTH_METHOD_PAP:
931                 phase2Method = WifiEnterpriseConfig.Phase2.PAP;
932                 break;
933             case Credential.UserCredential.AUTH_METHOD_MSCHAP:
934                 phase2Method = WifiEnterpriseConfig.Phase2.MSCHAP;
935                 break;
936             case Credential.UserCredential.AUTH_METHOD_MSCHAPV2:
937                 phase2Method = WifiEnterpriseConfig.Phase2.MSCHAPV2;
938                 break;
939             default:
940                 // Should never happen since this is already validated when the provider is
941                 // added.
942                 Log.wtf(TAG, "Unsupported Auth: " + credential.getNonEapInnerMethod());
943                 break;
944         }
945         config.setPhase2Method(phase2Method);
946     }
947 
948     /**
949      * Fill in WifiEnterpriseConfig with information from a certificate credential.
950      *
951      * @param config Instance of {@link WifiEnterpriseConfig}
952      */
buildEnterpriseConfigForCertCredential(WifiEnterpriseConfig config)953     private void buildEnterpriseConfigForCertCredential(WifiEnterpriseConfig config) {
954         config.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
955         config.setClientCertificateAlias(mClientPrivateKeyAndCertificateAlias);
956         if (!ArrayUtils.isEmpty(mCaCertificateAliases)) {
957             config.setCaCertificateAliases(mCaCertificateAliases.toArray(new String[0]));
958         } else {
959             config.setCaPath(SYSTEM_CA_STORE_PATH);
960         }
961     }
962 
963     /**
964      * Fill in WifiEnterpriseConfig with information from a SIM credential.
965      *
966      * @param config Instance of {@link WifiEnterpriseConfig}
967      * @param credential Instance of {@link SimCredential}
968      */
buildEnterpriseConfigForSimCredential(WifiEnterpriseConfig config, Credential.SimCredential credential)969     private void buildEnterpriseConfigForSimCredential(WifiEnterpriseConfig config,
970             Credential.SimCredential credential) {
971         int eapMethod = WifiEnterpriseConfig.Eap.NONE;
972         switch(credential.getEapType()) {
973             case EAPConstants.EAP_SIM:
974                 eapMethod = WifiEnterpriseConfig.Eap.SIM;
975                 break;
976             case EAPConstants.EAP_AKA:
977                 eapMethod = WifiEnterpriseConfig.Eap.AKA;
978                 break;
979             case EAPConstants.EAP_AKA_PRIME:
980                 eapMethod = WifiEnterpriseConfig.Eap.AKA_PRIME;
981                 break;
982             default:
983                 // Should never happen since this is already validated when the provider is
984                 // added.
985                 Log.wtf(TAG, "Unsupported EAP Method: " + credential.getEapType());
986                 break;
987         }
988         config.setEapMethod(eapMethod);
989         config.setPlmn(credential.getImsi());
990     }
991 
setAnonymousIdentityToNaiRealm(WifiEnterpriseConfig config, String realm)992     private static void setAnonymousIdentityToNaiRealm(WifiEnterpriseConfig config, String realm) {
993         /**
994          * Set WPA supplicant's anonymous identity field to a string containing the NAI realm, so
995          * that this value will be sent to the EAP server as part of the EAP-Response/ Identity
996          * packet. WPA supplicant will reset this field after using it for the EAP-Response/Identity
997          * packet, and revert to using the (real) identity field for subsequent transactions that
998          * request an identity (e.g. in EAP-TTLS).
999          *
1000          * This NAI realm value (the portion of the identity after the '@') is used to tell the
1001          * AAA server which AAA/H to forward packets to. The hardcoded username, "anonymous", is a
1002          * placeholder that is not used--it is set to this value by convention. See Section 5.1 of
1003          * RFC3748 for more details.
1004          *
1005          * NOTE: we do not set this value for EAP-SIM/AKA/AKA', since the EAP server expects the
1006          * EAP-Response/Identity packet to contain an actual, IMSI-based identity, in order to
1007          * identify the device.
1008          */
1009         config.setAnonymousIdentity("anonymous@" + realm);
1010     }
1011 
1012     /**
1013      * Helper function for creating a
1014      * {@link android.net.wifi.hotspot2.pps.Credential.UserCredential} from the given
1015      * {@link WifiEnterpriseConfig}
1016      *
1017      * @param config The enterprise configuration containing the credential
1018      * @return {@link android.net.wifi.hotspot2.pps.Credential.UserCredential}
1019      */
buildUserCredentialFromEnterpriseConfig( WifiEnterpriseConfig config)1020     private static Credential.UserCredential buildUserCredentialFromEnterpriseConfig(
1021             WifiEnterpriseConfig config) {
1022         Credential.UserCredential userCredential = new Credential.UserCredential();
1023         userCredential.setEapType(EAPConstants.EAP_TTLS);
1024 
1025         if (TextUtils.isEmpty(config.getIdentity())) {
1026             Log.e(TAG, "Missing username for user credential");
1027             return null;
1028         }
1029         userCredential.setUsername(config.getIdentity());
1030 
1031         if (TextUtils.isEmpty(config.getPassword())) {
1032             Log.e(TAG, "Missing password for user credential");
1033             return null;
1034         }
1035         String encodedPassword =
1036                 new String(Base64.encode(config.getPassword().getBytes(StandardCharsets.UTF_8),
1037                         Base64.DEFAULT), StandardCharsets.UTF_8);
1038         userCredential.setPassword(encodedPassword);
1039 
1040         switch(config.getPhase2Method()) {
1041             case WifiEnterpriseConfig.Phase2.PAP:
1042                 userCredential.setNonEapInnerMethod(Credential.UserCredential.AUTH_METHOD_PAP);
1043                 break;
1044             case WifiEnterpriseConfig.Phase2.MSCHAP:
1045                 userCredential.setNonEapInnerMethod(Credential.UserCredential.AUTH_METHOD_MSCHAP);
1046                 break;
1047             case WifiEnterpriseConfig.Phase2.MSCHAPV2:
1048                 userCredential.setNonEapInnerMethod(Credential.UserCredential.AUTH_METHOD_MSCHAPV2);
1049                 break;
1050             default:
1051                 Log.e(TAG, "Unsupported phase2 method for TTLS: " + config.getPhase2Method());
1052                 return null;
1053         }
1054         return userCredential;
1055     }
1056 
1057     /**
1058      * Helper function for creating a
1059      * {@link android.net.wifi.hotspot2.pps.Credential.SimCredential} from the given
1060      * {@link WifiEnterpriseConfig}
1061      *
1062      * @param eapType The EAP type of the SIM credential
1063      * @param config The enterprise configuration containing the credential
1064      * @return {@link android.net.wifi.hotspot2.pps.Credential.SimCredential}
1065      */
buildSimCredentialFromEnterpriseConfig( int eapType, WifiEnterpriseConfig config)1066     private static Credential.SimCredential buildSimCredentialFromEnterpriseConfig(
1067             int eapType, WifiEnterpriseConfig config) {
1068         Credential.SimCredential simCredential = new Credential.SimCredential();
1069         if (TextUtils.isEmpty(config.getPlmn())) {
1070             Log.e(TAG, "Missing IMSI for SIM credential");
1071             return null;
1072         }
1073         simCredential.setImsi(config.getPlmn());
1074         simCredential.setEapType(eapType);
1075         return simCredential;
1076     }
1077 
1078     /**
1079      * Enable verbose logging
1080      * @param verbose enables verbose logging
1081      */
enableVerboseLogging(boolean verbose)1082     public void enableVerboseLogging(boolean verbose) {
1083         mVerboseLoggingEnabled = verbose;
1084     }
1085 
1086     /**
1087      * Block a BSS or ESS following a Deauthentication-Imminent WNM-Notification
1088      *
1089      * @param bssid BSSID of the source AP
1090      * @param isEss true: Block ESS, false: Block BSS
1091      * @param delayInSeconds Delay duration in seconds
1092      */
blockBssOrEss(long bssid, boolean isEss, int delayInSeconds)1093     public void blockBssOrEss(long bssid, boolean isEss, int delayInSeconds) {
1094         if (delayInSeconds < 0 || bssid == 0) {
1095             return;
1096         }
1097 
1098         mReauthDelay = mClock.getElapsedSinceBootMillis();
1099         if (delayInSeconds == 0) {
1100             // Section 3.2.1.2 in the specification defines that a Re-Auth Delay field
1101             // value of 0 means the delay value is chosen by the mobile device.
1102             mReauthDelay += DEFAULT_BLOCKLIST_DURATION_MS;
1103         } else {
1104             mReauthDelay += (delayInSeconds * 1000);
1105         }
1106         if (isEss) {
1107             // Deauth-imminent for the entire ESS, do not try to reauthenticate until the delay
1108             // is over. Clear the list of blocked BSSIDs.
1109             mBlockedBssids.clear();
1110         } else {
1111             // Add this MAC address to the list of blocked BSSIDs.
1112             mBlockedBssids.add(Utils.macToString(bssid));
1113         }
1114     }
1115 
1116     /**
1117      * Clear a block from a Passpoint provider. Used when Wi-Fi state is cleared, for example,
1118      * when turning Wi-Fi off.
1119      */
clearProviderBlock()1120     public void clearProviderBlock() {
1121         mReauthDelay = 0;
1122         mBlockedBssids.clear();
1123     }
1124 
1125     /**
1126      * Checks if this provider is blocked or if there are any BSSes blocked
1127      *
1128      * @param scanResult Latest scan result
1129      * @return true if blocked, false otherwise
1130      */
isProviderBlocked(ScanResult scanResult)1131     private boolean isProviderBlocked(ScanResult scanResult) {
1132         if (mReauthDelay == 0) {
1133             return false;
1134         }
1135 
1136         if (mClock.getElapsedSinceBootMillis() >= mReauthDelay) {
1137             // Provider was blocked, but the delay duration have passed
1138             mReauthDelay = 0;
1139             mBlockedBssids.clear();
1140             return false;
1141         }
1142 
1143         // Empty means the entire ESS is blocked
1144         if (mBlockedBssids.isEmpty() || mBlockedBssids.contains(scanResult.BSSID)) {
1145             return true;
1146         }
1147 
1148         // Trying to associate to another BSS in the ESS
1149         return false;
1150     }
1151 
1152     /**
1153      * Set the user connect choice on the passpoint network.
1154      * @param choice The {@link WifiConfiguration#getProfileKey()} of the user connect
1155      *               network.
1156      * @param rssi The signal strength of the network.
1157      */
setUserConnectChoice(String choice, int rssi)1158     public void setUserConnectChoice(String choice, int rssi) {
1159         mConnectChoice = choice;
1160         mConnectChoiceRssi = rssi;
1161     }
1162 
getConnectChoice()1163     public String getConnectChoice() {
1164         return mConnectChoice;
1165     }
1166 
getConnectChoiceRssi()1167     public int getConnectChoiceRssi() {
1168         return mConnectChoiceRssi;
1169     }
1170 }
1171