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