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