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