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