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 android.annotation.Nullable; 20 import android.net.wifi.EAPConstants; 21 import android.net.wifi.WifiConfiguration; 22 import android.net.wifi.WifiEnterpriseConfig; 23 import android.net.wifi.hotspot2.PasspointConfiguration; 24 import android.net.wifi.hotspot2.pps.Credential; 25 import android.net.wifi.hotspot2.pps.Credential.SimCredential; 26 import android.net.wifi.hotspot2.pps.Credential.UserCredential; 27 import android.net.wifi.hotspot2.pps.HomeSp; 28 import android.security.Credentials; 29 import android.text.TextUtils; 30 import android.util.Base64; 31 import android.util.Log; 32 33 import com.android.server.wifi.IMSIParameter; 34 import com.android.server.wifi.SIMAccessor; 35 import com.android.server.wifi.WifiKeyStore; 36 import com.android.server.wifi.hotspot2.anqp.ANQPElement; 37 import com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType; 38 import com.android.server.wifi.hotspot2.anqp.DomainNameElement; 39 import com.android.server.wifi.hotspot2.anqp.NAIRealmElement; 40 import com.android.server.wifi.hotspot2.anqp.RoamingConsortiumElement; 41 import com.android.server.wifi.hotspot2.anqp.ThreeGPPNetworkElement; 42 import com.android.server.wifi.hotspot2.anqp.eap.AuthParam; 43 import com.android.server.wifi.hotspot2.anqp.eap.NonEAPInnerAuth; 44 import com.android.server.wifi.util.InformationElementUtil.RoamingConsortium; 45 46 import java.nio.charset.StandardCharsets; 47 import java.security.MessageDigest; 48 import java.security.NoSuchAlgorithmException; 49 import java.security.cert.CertificateEncodingException; 50 import java.security.cert.X509Certificate; 51 import java.util.ArrayList; 52 import java.util.Arrays; 53 import java.util.List; 54 import java.util.Map; 55 import java.util.Objects; 56 57 /** 58 * Abstraction for Passpoint service provider. This class contains the both static 59 * Passpoint configuration data and the runtime data (e.g. blacklisted SSIDs, statistics). 60 */ 61 public class PasspointProvider { 62 private static final String TAG = "PasspointProvider"; 63 64 /** 65 * Used as part of alias string for certificates and keys. The alias string is in the format 66 * of: [KEY_TYPE]_HS2_[ProviderID] 67 * For example: "CACERT_HS2_0", "USRCERT_HS2_0", "USRPKEY_HS2_0", "CACERT_HS2_REMEDIATION_0" 68 */ 69 private static final String ALIAS_HS_TYPE = "HS2_"; 70 private static final String ALIAS_ALIAS_REMEDIATION_TYPE = "REMEDIATION_"; 71 72 private final PasspointConfiguration mConfig; 73 private final WifiKeyStore mKeyStore; 74 75 /** 76 * Aliases for the private keys and certificates installed in the keystore. Each alias 77 * is a suffix of the actual certificate or key name installed in the keystore. The 78 * certificate or key name in the keystore is consist of |Type|_|alias|. 79 * This will be consistent with the usage of the term "alias" in {@link WifiEnterpriseConfig}. 80 */ 81 private List<String> mCaCertificateAliases; 82 private String mClientPrivateKeyAlias; 83 private String mClientCertificateAlias; 84 private String mRemediationCaCertificateAlias; 85 86 private final long mProviderId; 87 private final int mCreatorUid; 88 private final String mPackageName; 89 90 private final IMSIParameter mImsiParameter; 91 private final List<String> mMatchingSIMImsiList; 92 93 private final int mEAPMethodID; 94 private final AuthParam mAuthParam; 95 96 private boolean mHasEverConnected; 97 private boolean mIsShared; 98 99 /** 100 * This is a flag to indicate if the Provider is created temporarily. 101 * Thus, it is not saved permanently unlike normal Passpoint profile. 102 */ 103 private boolean mIsEphemeral = false; 104 PasspointProvider(PasspointConfiguration config, WifiKeyStore keyStore, SIMAccessor simAccessor, long providerId, int creatorUid, String packageName)105 public PasspointProvider(PasspointConfiguration config, WifiKeyStore keyStore, 106 SIMAccessor simAccessor, long providerId, int creatorUid, String packageName) { 107 this(config, keyStore, simAccessor, providerId, creatorUid, packageName, null, null, null, 108 null, false, false); 109 } 110 PasspointProvider(PasspointConfiguration config, WifiKeyStore keyStore, SIMAccessor simAccessor, long providerId, int creatorUid, String packageName, List<String> caCertificateAliases, String clientCertificateAlias, String clientPrivateKeyAlias, String remediationCaCertificateAlias, boolean hasEverConnected, boolean isShared)111 public PasspointProvider(PasspointConfiguration config, WifiKeyStore keyStore, 112 SIMAccessor simAccessor, long providerId, int creatorUid, String packageName, 113 List<String> caCertificateAliases, 114 String clientCertificateAlias, String clientPrivateKeyAlias, 115 String remediationCaCertificateAlias, 116 boolean hasEverConnected, boolean isShared) { 117 // Maintain a copy of the configuration to avoid it being updated by others. 118 mConfig = new PasspointConfiguration(config); 119 mKeyStore = keyStore; 120 mProviderId = providerId; 121 mCreatorUid = creatorUid; 122 mPackageName = packageName; 123 mCaCertificateAliases = caCertificateAliases; 124 mClientCertificateAlias = clientCertificateAlias; 125 mClientPrivateKeyAlias = clientPrivateKeyAlias; 126 mRemediationCaCertificateAlias = remediationCaCertificateAlias; 127 mHasEverConnected = hasEverConnected; 128 mIsShared = isShared; 129 130 // Setup EAP method and authentication parameter based on the credential. 131 if (mConfig.getCredential().getUserCredential() != null) { 132 mEAPMethodID = EAPConstants.EAP_TTLS; 133 mAuthParam = new NonEAPInnerAuth(NonEAPInnerAuth.getAuthTypeID( 134 mConfig.getCredential().getUserCredential().getNonEapInnerMethod())); 135 mImsiParameter = null; 136 mMatchingSIMImsiList = null; 137 } else if (mConfig.getCredential().getCertCredential() != null) { 138 mEAPMethodID = EAPConstants.EAP_TLS; 139 mAuthParam = null; 140 mImsiParameter = null; 141 mMatchingSIMImsiList = null; 142 } else { 143 mEAPMethodID = mConfig.getCredential().getSimCredential().getEapType(); 144 mAuthParam = null; 145 mImsiParameter = IMSIParameter.build( 146 mConfig.getCredential().getSimCredential().getImsi()); 147 mMatchingSIMImsiList = simAccessor.getMatchingImsis(mImsiParameter); 148 } 149 } 150 getConfig()151 public PasspointConfiguration getConfig() { 152 // Return a copy of the configuration to avoid it being updated by others. 153 return new PasspointConfiguration(mConfig); 154 } 155 getCaCertificateAliases()156 public List<String> getCaCertificateAliases() { 157 return mCaCertificateAliases; 158 } 159 getClientPrivateKeyAlias()160 public String getClientPrivateKeyAlias() { 161 return mClientPrivateKeyAlias; 162 } 163 getClientCertificateAlias()164 public String getClientCertificateAlias() { 165 return mClientCertificateAlias; 166 } 167 getRemediationCaCertificateAlias()168 public String getRemediationCaCertificateAlias() { 169 return mRemediationCaCertificateAlias; 170 } 171 getProviderId()172 public long getProviderId() { 173 return mProviderId; 174 } 175 getCreatorUid()176 public int getCreatorUid() { 177 return mCreatorUid; 178 } 179 180 @Nullable getPackageName()181 public String getPackageName() { 182 return mPackageName; 183 } 184 getHasEverConnected()185 public boolean getHasEverConnected() { 186 return mHasEverConnected; 187 } 188 setHasEverConnected(boolean hasEverConnected)189 public void setHasEverConnected(boolean hasEverConnected) { 190 mHasEverConnected = hasEverConnected; 191 } 192 isEphemeral()193 public boolean isEphemeral() { 194 return mIsEphemeral; 195 } 196 setEphemeral(boolean isEphemeral)197 public void setEphemeral(boolean isEphemeral) { 198 mIsEphemeral = isEphemeral; 199 } 200 getImsiParameter()201 public IMSIParameter getImsiParameter() { 202 return mImsiParameter; 203 } 204 205 /** 206 * Install certificates and key based on current configuration. 207 * Note: the certificates and keys in the configuration will get cleared once 208 * they're installed in the keystore. 209 * 210 * @return true on success 211 */ installCertsAndKeys()212 public boolean installCertsAndKeys() { 213 // Install CA certificate. 214 X509Certificate[] x509Certificates = mConfig.getCredential().getCaCertificates(); 215 if (x509Certificates != null) { 216 mCaCertificateAliases = new ArrayList<>(); 217 for (int i = 0; i < x509Certificates.length; i++) { 218 String alias = String.format("%s%s_%d", ALIAS_HS_TYPE, mProviderId, i); 219 if (!mKeyStore.putCertInKeyStore(Credentials.CA_CERTIFICATE + alias, 220 x509Certificates[i])) { 221 Log.e(TAG, "Failed to install CA Certificate"); 222 uninstallCertsAndKeys(); 223 return false; 224 } else { 225 mCaCertificateAliases.add(alias); 226 } 227 } 228 } 229 230 // Install the client private key. 231 if (mConfig.getCredential().getClientPrivateKey() != null) { 232 String keyName = Credentials.USER_PRIVATE_KEY + ALIAS_HS_TYPE + mProviderId; 233 if (!mKeyStore.putKeyInKeyStore(keyName, 234 mConfig.getCredential().getClientPrivateKey())) { 235 Log.e(TAG, "Failed to install client private key"); 236 uninstallCertsAndKeys(); 237 return false; 238 } 239 mClientPrivateKeyAlias = ALIAS_HS_TYPE + mProviderId; 240 } 241 242 // Install the client certificate. 243 if (mConfig.getCredential().getClientCertificateChain() != null) { 244 X509Certificate clientCert = getClientCertificate( 245 mConfig.getCredential().getClientCertificateChain(), 246 mConfig.getCredential().getCertCredential().getCertSha256Fingerprint()); 247 if (clientCert == null) { 248 Log.e(TAG, "Failed to locate client certificate"); 249 uninstallCertsAndKeys(); 250 return false; 251 } 252 String certName = Credentials.USER_CERTIFICATE + ALIAS_HS_TYPE + mProviderId; 253 if (!mKeyStore.putCertInKeyStore(certName, clientCert)) { 254 Log.e(TAG, "Failed to install client certificate"); 255 uninstallCertsAndKeys(); 256 return false; 257 } 258 mClientCertificateAlias = ALIAS_HS_TYPE + mProviderId; 259 } 260 261 if (mConfig.getSubscriptionUpdate() != null) { 262 X509Certificate certificate = mConfig.getSubscriptionUpdate().getCaCertificate(); 263 if (certificate == null) { 264 Log.e(TAG, "Failed to locate CA certificate for remediation"); 265 uninstallCertsAndKeys(); 266 return false; 267 } 268 mRemediationCaCertificateAlias = 269 ALIAS_HS_TYPE + ALIAS_ALIAS_REMEDIATION_TYPE + mProviderId; 270 String certName = Credentials.CA_CERTIFICATE + mRemediationCaCertificateAlias; 271 if (!mKeyStore.putCertInKeyStore(certName, certificate)) { 272 Log.e(TAG, "Failed to install CA certificate for remediation"); 273 mRemediationCaCertificateAlias = null; 274 uninstallCertsAndKeys(); 275 return false; 276 } 277 } 278 279 // Clear the keys and certificates in the configuration. 280 mConfig.getCredential().setCaCertificates(null); 281 mConfig.getCredential().setClientPrivateKey(null); 282 mConfig.getCredential().setClientCertificateChain(null); 283 if (mConfig.getSubscriptionUpdate() != null) { 284 mConfig.getSubscriptionUpdate().setCaCertificate(null); 285 } 286 return true; 287 } 288 289 /** 290 * Remove any installed certificates and key. 291 */ uninstallCertsAndKeys()292 public void uninstallCertsAndKeys() { 293 if (mCaCertificateAliases != null) { 294 for (String certificateAlias : mCaCertificateAliases) { 295 if (!mKeyStore.removeEntryFromKeyStore( 296 Credentials.CA_CERTIFICATE + certificateAlias)) { 297 Log.e(TAG, "Failed to remove entry: " + certificateAlias); 298 } 299 } 300 mCaCertificateAliases = null; 301 } 302 if (mClientPrivateKeyAlias != null) { 303 if (!mKeyStore.removeEntryFromKeyStore( 304 Credentials.USER_PRIVATE_KEY + mClientPrivateKeyAlias)) { 305 Log.e(TAG, "Failed to remove entry: " + mClientPrivateKeyAlias); 306 } 307 mClientPrivateKeyAlias = null; 308 } 309 if (mClientCertificateAlias != null) { 310 if (!mKeyStore.removeEntryFromKeyStore( 311 Credentials.USER_CERTIFICATE + mClientCertificateAlias)) { 312 Log.e(TAG, "Failed to remove entry: " + mClientCertificateAlias); 313 } 314 mClientCertificateAlias = null; 315 } 316 317 if (mRemediationCaCertificateAlias != null) { 318 if (!mKeyStore.removeEntryFromKeyStore( 319 Credentials.CA_CERTIFICATE + mRemediationCaCertificateAlias)) { 320 Log.e(TAG, "Failed to remove entry: " + mRemediationCaCertificateAlias); 321 } 322 mRemediationCaCertificateAlias = null; 323 } 324 } 325 326 /** 327 * Return the matching status with the given AP, based on the ANQP elements from the AP. 328 * 329 * @param anqpElements ANQP elements from the AP 330 * @param roamingConsortium Roaming Consortium information element from the AP 331 * @return {@link PasspointMatch} 332 */ match(Map<ANQPElementType, ANQPElement> anqpElements, RoamingConsortium roamingConsortium)333 public PasspointMatch match(Map<ANQPElementType, ANQPElement> anqpElements, 334 RoamingConsortium roamingConsortium) { 335 PasspointMatch providerMatch = matchProviderExceptFor3GPP(anqpElements, roamingConsortium); 336 337 // 3GPP Network matching. 338 if (providerMatch == PasspointMatch.None && ANQPMatcher.matchThreeGPPNetwork( 339 (ThreeGPPNetworkElement) anqpElements.get(ANQPElementType.ANQP3GPPNetwork), 340 mImsiParameter, mMatchingSIMImsiList)) { 341 return PasspointMatch.RoamingProvider; 342 } 343 344 // Perform authentication match against the NAI Realm. 345 int authMatch = ANQPMatcher.matchNAIRealm( 346 (NAIRealmElement) anqpElements.get(ANQPElementType.ANQPNAIRealm), 347 mConfig.getCredential().getRealm(), mEAPMethodID, mAuthParam); 348 349 // In case of Auth mismatch, demote provider match. 350 if (authMatch == AuthMatch.NONE) { 351 return PasspointMatch.None; 352 } 353 354 // In case of no realm match, return provider match as is. 355 if ((authMatch & AuthMatch.REALM) == 0) { 356 return providerMatch; 357 } 358 359 // Promote the provider match to roaming provider if provider match is not found, but NAI 360 // realm is matched. 361 return providerMatch == PasspointMatch.None ? PasspointMatch.RoamingProvider 362 : providerMatch; 363 } 364 365 /** 366 * Generate a WifiConfiguration based on the provider's configuration. The generated 367 * WifiConfiguration will include all the necessary credentials for network connection except 368 * the SSID, which should be added by the caller when the config is being used for network 369 * connection. 370 * 371 * @return {@link WifiConfiguration} 372 */ getWifiConfig()373 public WifiConfiguration getWifiConfig() { 374 WifiConfiguration wifiConfig = new WifiConfiguration(); 375 wifiConfig.FQDN = mConfig.getHomeSp().getFqdn(); 376 if (mConfig.getHomeSp().getRoamingConsortiumOis() != null) { 377 wifiConfig.roamingConsortiumIds = Arrays.copyOf( 378 mConfig.getHomeSp().getRoamingConsortiumOis(), 379 mConfig.getHomeSp().getRoamingConsortiumOis().length); 380 } 381 if (mConfig.getUpdateIdentifier() != Integer.MIN_VALUE) { 382 // R2 profile, it needs to set updateIdentifier HS2.0 Indication element as PPS MO 383 // ID in Association Request. 384 wifiConfig.updateIdentifier = Integer.toString(mConfig.getUpdateIdentifier()); 385 if (isMeteredNetwork(mConfig)) { 386 wifiConfig.meteredOverride = WifiConfiguration.METERED_OVERRIDE_METERED; 387 } 388 } 389 wifiConfig.providerFriendlyName = mConfig.getHomeSp().getFriendlyName(); 390 wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP); 391 wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X); 392 393 // Set RSN only to tell wpa_supplicant that this network is for Passpoint. 394 wifiConfig.allowedProtocols.set(WifiConfiguration.Protocol.RSN); 395 396 WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig(); 397 enterpriseConfig.setRealm(mConfig.getCredential().getRealm()); 398 enterpriseConfig.setDomainSuffixMatch(mConfig.getHomeSp().getFqdn()); 399 if (mConfig.getCredential().getUserCredential() != null) { 400 buildEnterpriseConfigForUserCredential(enterpriseConfig, 401 mConfig.getCredential().getUserCredential()); 402 setAnonymousIdentityToNaiRealm(enterpriseConfig, mConfig.getCredential().getRealm()); 403 } else if (mConfig.getCredential().getCertCredential() != null) { 404 buildEnterpriseConfigForCertCredential(enterpriseConfig); 405 setAnonymousIdentityToNaiRealm(enterpriseConfig, mConfig.getCredential().getRealm()); 406 } else { 407 buildEnterpriseConfigForSimCredential(enterpriseConfig, 408 mConfig.getCredential().getSimCredential()); 409 } 410 wifiConfig.enterpriseConfig = enterpriseConfig; 411 wifiConfig.shared = mIsShared; 412 return wifiConfig; 413 } 414 415 /** 416 * @return true if provider is backed by a SIM credential. 417 */ isSimCredential()418 public boolean isSimCredential() { 419 return mConfig.getCredential().getSimCredential() != null; 420 } 421 422 /** 423 * Convert a legacy {@link WifiConfiguration} representation of a Passpoint configuration to 424 * a {@link PasspointConfiguration}. This is used for migrating legacy Passpoint 425 * configuration (release N and older). 426 * 427 * @param wifiConfig The {@link WifiConfiguration} to convert 428 * @return {@link PasspointConfiguration} 429 */ convertFromWifiConfig(WifiConfiguration wifiConfig)430 public static PasspointConfiguration convertFromWifiConfig(WifiConfiguration wifiConfig) { 431 PasspointConfiguration passpointConfig = new PasspointConfiguration(); 432 433 // Setup HomeSP. 434 HomeSp homeSp = new HomeSp(); 435 if (TextUtils.isEmpty(wifiConfig.FQDN)) { 436 Log.e(TAG, "Missing FQDN"); 437 return null; 438 } 439 homeSp.setFqdn(wifiConfig.FQDN); 440 homeSp.setFriendlyName(wifiConfig.providerFriendlyName); 441 if (wifiConfig.roamingConsortiumIds != null) { 442 homeSp.setRoamingConsortiumOis(Arrays.copyOf( 443 wifiConfig.roamingConsortiumIds, wifiConfig.roamingConsortiumIds.length)); 444 } 445 passpointConfig.setHomeSp(homeSp); 446 447 // Setup Credential. 448 Credential credential = new Credential(); 449 credential.setRealm(wifiConfig.enterpriseConfig.getRealm()); 450 switch (wifiConfig.enterpriseConfig.getEapMethod()) { 451 case WifiEnterpriseConfig.Eap.TTLS: 452 credential.setUserCredential(buildUserCredentialFromEnterpriseConfig( 453 wifiConfig.enterpriseConfig)); 454 break; 455 case WifiEnterpriseConfig.Eap.TLS: 456 Credential.CertificateCredential certCred = new Credential.CertificateCredential(); 457 certCred.setCertType(Credential.CertificateCredential.CERT_TYPE_X509V3); 458 credential.setCertCredential(certCred); 459 break; 460 case WifiEnterpriseConfig.Eap.SIM: 461 credential.setSimCredential(buildSimCredentialFromEnterpriseConfig( 462 EAPConstants.EAP_SIM, wifiConfig.enterpriseConfig)); 463 break; 464 case WifiEnterpriseConfig.Eap.AKA: 465 credential.setSimCredential(buildSimCredentialFromEnterpriseConfig( 466 EAPConstants.EAP_AKA, wifiConfig.enterpriseConfig)); 467 break; 468 case WifiEnterpriseConfig.Eap.AKA_PRIME: 469 credential.setSimCredential(buildSimCredentialFromEnterpriseConfig( 470 EAPConstants.EAP_AKA_PRIME, wifiConfig.enterpriseConfig)); 471 break; 472 default: 473 Log.e(TAG, "Unsupport EAP method: " + wifiConfig.enterpriseConfig.getEapMethod()); 474 return null; 475 } 476 if (credential.getUserCredential() == null && credential.getCertCredential() == null 477 && credential.getSimCredential() == null) { 478 Log.e(TAG, "Missing credential"); 479 return null; 480 } 481 passpointConfig.setCredential(credential); 482 483 return passpointConfig; 484 } 485 486 @Override equals(Object thatObject)487 public boolean equals(Object thatObject) { 488 if (this == thatObject) { 489 return true; 490 } 491 if (!(thatObject instanceof PasspointProvider)) { 492 return false; 493 } 494 PasspointProvider that = (PasspointProvider) thatObject; 495 return mProviderId == that.mProviderId 496 && (mCaCertificateAliases == null ? that.mCaCertificateAliases == null 497 : mCaCertificateAliases.equals(that.mCaCertificateAliases)) 498 && TextUtils.equals(mClientCertificateAlias, that.mClientCertificateAlias) 499 && TextUtils.equals(mClientPrivateKeyAlias, that.mClientPrivateKeyAlias) 500 && (mConfig == null ? that.mConfig == null : mConfig.equals(that.mConfig)) 501 && TextUtils.equals(mRemediationCaCertificateAlias, 502 that.mRemediationCaCertificateAlias); 503 } 504 505 @Override hashCode()506 public int hashCode() { 507 return Objects.hash(mProviderId, mCaCertificateAliases, mClientCertificateAlias, 508 mClientPrivateKeyAlias, mConfig, mRemediationCaCertificateAlias); 509 } 510 511 @Override toString()512 public String toString() { 513 StringBuilder builder = new StringBuilder(); 514 builder.append("ProviderId: ").append(mProviderId).append("\n"); 515 builder.append("CreatorUID: ").append(mCreatorUid).append("\n"); 516 if (mPackageName != null) { 517 builder.append("PackageName: ").append(mPackageName).append("\n"); 518 } 519 builder.append("Configuration Begin ---\n"); 520 builder.append(mConfig); 521 builder.append("Configuration End ---\n"); 522 return builder.toString(); 523 } 524 525 /** 526 * Retrieve the client certificate from the certificates chain. The certificate 527 * with the matching SHA256 digest is the client certificate. 528 * 529 * @param certChain The client certificates chain 530 * @param expectedSha256Fingerprint The expected SHA256 digest of the client certificate 531 * @return {@link java.security.cert.X509Certificate} 532 */ getClientCertificate(X509Certificate[] certChain, byte[] expectedSha256Fingerprint)533 private static X509Certificate getClientCertificate(X509Certificate[] certChain, 534 byte[] expectedSha256Fingerprint) { 535 if (certChain == null) { 536 return null; 537 } 538 try { 539 MessageDigest digester = MessageDigest.getInstance("SHA-256"); 540 for (X509Certificate certificate : certChain) { 541 digester.reset(); 542 byte[] fingerprint = digester.digest(certificate.getEncoded()); 543 if (Arrays.equals(expectedSha256Fingerprint, fingerprint)) { 544 return certificate; 545 } 546 } 547 } catch (CertificateEncodingException | NoSuchAlgorithmException e) { 548 return null; 549 } 550 551 return null; 552 } 553 554 /** 555 * Determines the Passpoint network is a metered network. 556 * 557 * Expiration date -> non-metered 558 * Data limit -> metered 559 * Time usage limit -> metered 560 * @param passpointConfig instance of {@link PasspointConfiguration} 561 * @return {@code true} if the network is a metered network, {@code false} otherwise. 562 */ isMeteredNetwork(PasspointConfiguration passpointConfig)563 private boolean isMeteredNetwork(PasspointConfiguration passpointConfig) { 564 if (passpointConfig == null) return false; 565 566 // If DataLimit is zero, there is unlimited data usage for the account. 567 // If TimeLimit is zero, there is unlimited time usage for the account. 568 return passpointConfig.getUsageLimitDataLimit() > 0 569 || passpointConfig.getUsageLimitTimeLimitInMinutes() > 0; 570 } 571 572 /** 573 * Perform a provider match based on the given ANQP elements except for matching 3GPP Network. 574 * 575 * @param anqpElements List of ANQP elements 576 * @param roamingConsortium Roaming Consortium information element from the AP 577 * @return {@link PasspointMatch} 578 */ matchProviderExceptFor3GPP( Map<ANQPElementType, ANQPElement> anqpElements, RoamingConsortium roamingConsortium)579 private PasspointMatch matchProviderExceptFor3GPP( 580 Map<ANQPElementType, ANQPElement> anqpElements, 581 RoamingConsortium roamingConsortium) { 582 // Domain name matching. 583 if (ANQPMatcher.matchDomainName( 584 (DomainNameElement) anqpElements.get(ANQPElementType.ANQPDomName), 585 mConfig.getHomeSp().getFqdn(), mImsiParameter, mMatchingSIMImsiList)) { 586 return PasspointMatch.HomeProvider; 587 } 588 589 // ANQP Roaming Consortium OI matching. 590 long[] providerOIs = mConfig.getHomeSp().getRoamingConsortiumOis(); 591 if (ANQPMatcher.matchRoamingConsortium( 592 (RoamingConsortiumElement) anqpElements.get(ANQPElementType.ANQPRoamingConsortium), 593 providerOIs)) { 594 return PasspointMatch.RoamingProvider; 595 } 596 597 long[] roamingConsortiums = roamingConsortium.getRoamingConsortiums(); 598 // Roaming Consortium OI information element matching. 599 if (roamingConsortiums != null && providerOIs != null) { 600 for (long sta_oi: roamingConsortiums) { 601 for (long ap_oi: providerOIs) { 602 if (sta_oi == ap_oi) { 603 return PasspointMatch.RoamingProvider; 604 } 605 } 606 } 607 } 608 609 return PasspointMatch.None; 610 } 611 612 /** 613 * Fill in WifiEnterpriseConfig with information from an user credential. 614 * 615 * @param config Instance of {@link WifiEnterpriseConfig} 616 * @param credential Instance of {@link UserCredential} 617 */ buildEnterpriseConfigForUserCredential(WifiEnterpriseConfig config, Credential.UserCredential credential)618 private void buildEnterpriseConfigForUserCredential(WifiEnterpriseConfig config, 619 Credential.UserCredential credential) { 620 byte[] pwOctets = Base64.decode(credential.getPassword(), Base64.DEFAULT); 621 String decodedPassword = new String(pwOctets, StandardCharsets.UTF_8); 622 config.setEapMethod(WifiEnterpriseConfig.Eap.TTLS); 623 config.setIdentity(credential.getUsername()); 624 config.setPassword(decodedPassword); 625 config.setCaCertificateAliases(mCaCertificateAliases.toArray(new String[0])); 626 int phase2Method = WifiEnterpriseConfig.Phase2.NONE; 627 switch (credential.getNonEapInnerMethod()) { 628 case Credential.UserCredential.AUTH_METHOD_PAP: 629 phase2Method = WifiEnterpriseConfig.Phase2.PAP; 630 break; 631 case Credential.UserCredential.AUTH_METHOD_MSCHAP: 632 phase2Method = WifiEnterpriseConfig.Phase2.MSCHAP; 633 break; 634 case Credential.UserCredential.AUTH_METHOD_MSCHAPV2: 635 phase2Method = WifiEnterpriseConfig.Phase2.MSCHAPV2; 636 break; 637 default: 638 // Should never happen since this is already validated when the provider is 639 // added. 640 Log.wtf(TAG, "Unsupported Auth: " + credential.getNonEapInnerMethod()); 641 break; 642 } 643 config.setPhase2Method(phase2Method); 644 } 645 646 /** 647 * Fill in WifiEnterpriseConfig with information from a certificate credential. 648 * 649 * @param config Instance of {@link WifiEnterpriseConfig} 650 */ buildEnterpriseConfigForCertCredential(WifiEnterpriseConfig config)651 private void buildEnterpriseConfigForCertCredential(WifiEnterpriseConfig config) { 652 config.setEapMethod(WifiEnterpriseConfig.Eap.TLS); 653 config.setClientCertificateAlias(mClientCertificateAlias); 654 config.setCaCertificateAliases(mCaCertificateAliases.toArray(new String[0])); 655 } 656 657 /** 658 * Fill in WifiEnterpriseConfig with information from a SIM credential. 659 * 660 * @param config Instance of {@link WifiEnterpriseConfig} 661 * @param credential Instance of {@link SimCredential} 662 */ buildEnterpriseConfigForSimCredential(WifiEnterpriseConfig config, Credential.SimCredential credential)663 private void buildEnterpriseConfigForSimCredential(WifiEnterpriseConfig config, 664 Credential.SimCredential credential) { 665 int eapMethod = WifiEnterpriseConfig.Eap.NONE; 666 switch(credential.getEapType()) { 667 case EAPConstants.EAP_SIM: 668 eapMethod = WifiEnterpriseConfig.Eap.SIM; 669 break; 670 case EAPConstants.EAP_AKA: 671 eapMethod = WifiEnterpriseConfig.Eap.AKA; 672 break; 673 case EAPConstants.EAP_AKA_PRIME: 674 eapMethod = WifiEnterpriseConfig.Eap.AKA_PRIME; 675 break; 676 default: 677 // Should never happen since this is already validated when the provider is 678 // added. 679 Log.wtf(TAG, "Unsupported EAP Method: " + credential.getEapType()); 680 break; 681 } 682 config.setEapMethod(eapMethod); 683 config.setPlmn(credential.getImsi()); 684 } 685 setAnonymousIdentityToNaiRealm(WifiEnterpriseConfig config, String realm)686 private static void setAnonymousIdentityToNaiRealm(WifiEnterpriseConfig config, String realm) { 687 /** 688 * Set WPA supplicant's anonymous identity field to a string containing the NAI realm, so 689 * that this value will be sent to the EAP server as part of the EAP-Response/ Identity 690 * packet. WPA supplicant will reset this field after using it for the EAP-Response/Identity 691 * packet, and revert to using the (real) identity field for subsequent transactions that 692 * request an identity (e.g. in EAP-TTLS). 693 * 694 * This NAI realm value (the portion of the identity after the '@') is used to tell the 695 * AAA server which AAA/H to forward packets to. The hardcoded username, "anonymous", is a 696 * placeholder that is not used--it is set to this value by convention. See Section 5.1 of 697 * RFC3748 for more details. 698 * 699 * NOTE: we do not set this value for EAP-SIM/AKA/AKA', since the EAP server expects the 700 * EAP-Response/Identity packet to contain an actual, IMSI-based identity, in order to 701 * identify the device. 702 */ 703 config.setAnonymousIdentity("anonymous@" + realm); 704 } 705 706 /** 707 * Helper function for creating a 708 * {@link android.net.wifi.hotspot2.pps.Credential.UserCredential} from the given 709 * {@link WifiEnterpriseConfig} 710 * 711 * @param config The enterprise configuration containing the credential 712 * @return {@link android.net.wifi.hotspot2.pps.Credential.UserCredential} 713 */ buildUserCredentialFromEnterpriseConfig( WifiEnterpriseConfig config)714 private static Credential.UserCredential buildUserCredentialFromEnterpriseConfig( 715 WifiEnterpriseConfig config) { 716 Credential.UserCredential userCredential = new Credential.UserCredential(); 717 userCredential.setEapType(EAPConstants.EAP_TTLS); 718 719 if (TextUtils.isEmpty(config.getIdentity())) { 720 Log.e(TAG, "Missing username for user credential"); 721 return null; 722 } 723 userCredential.setUsername(config.getIdentity()); 724 725 if (TextUtils.isEmpty(config.getPassword())) { 726 Log.e(TAG, "Missing password for user credential"); 727 return null; 728 } 729 String encodedPassword = 730 new String(Base64.encode(config.getPassword().getBytes(StandardCharsets.UTF_8), 731 Base64.DEFAULT), StandardCharsets.UTF_8); 732 userCredential.setPassword(encodedPassword); 733 734 switch(config.getPhase2Method()) { 735 case WifiEnterpriseConfig.Phase2.PAP: 736 userCredential.setNonEapInnerMethod(Credential.UserCredential.AUTH_METHOD_PAP); 737 break; 738 case WifiEnterpriseConfig.Phase2.MSCHAP: 739 userCredential.setNonEapInnerMethod(Credential.UserCredential.AUTH_METHOD_MSCHAP); 740 break; 741 case WifiEnterpriseConfig.Phase2.MSCHAPV2: 742 userCredential.setNonEapInnerMethod(Credential.UserCredential.AUTH_METHOD_MSCHAPV2); 743 break; 744 default: 745 Log.e(TAG, "Unsupported phase2 method for TTLS: " + config.getPhase2Method()); 746 return null; 747 } 748 return userCredential; 749 } 750 751 /** 752 * Helper function for creating a 753 * {@link android.net.wifi.hotspot2.pps.Credential.SimCredential} from the given 754 * {@link WifiEnterpriseConfig} 755 * 756 * @param eapType The EAP type of the SIM credential 757 * @param config The enterprise configuration containing the credential 758 * @return {@link android.net.wifi.hotspot2.pps.Credential.SimCredential} 759 */ buildSimCredentialFromEnterpriseConfig( int eapType, WifiEnterpriseConfig config)760 private static Credential.SimCredential buildSimCredentialFromEnterpriseConfig( 761 int eapType, WifiEnterpriseConfig config) { 762 Credential.SimCredential simCredential = new Credential.SimCredential(); 763 if (TextUtils.isEmpty(config.getPlmn())) { 764 Log.e(TAG, "Missing IMSI for SIM credential"); 765 return null; 766 } 767 simCredential.setImsi(config.getPlmn()); 768 simCredential.setEapType(eapType); 769 return simCredential; 770 } 771 } 772