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