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