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