1 /* 2 * Copyright (C) 2013 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 package android.net.wifi; 17 18 import android.annotation.IntDef; 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.SystemApi; 22 import android.compat.annotation.UnsupportedAppUsage; 23 import android.os.Build; 24 import android.os.Parcel; 25 import android.os.Parcelable; 26 import android.text.TextUtils; 27 import android.util.Log; 28 29 import androidx.annotation.RequiresApi; 30 31 import com.android.modules.utils.build.SdkLevel; 32 33 import java.lang.annotation.Retention; 34 import java.lang.annotation.RetentionPolicy; 35 import java.nio.charset.StandardCharsets; 36 import java.security.PrivateKey; 37 import java.security.cert.X509Certificate; 38 import java.security.interfaces.ECPublicKey; 39 import java.security.interfaces.RSAPublicKey; 40 import java.security.spec.ECParameterSpec; 41 import java.util.Arrays; 42 import java.util.HashMap; 43 import java.util.List; 44 import java.util.Map; 45 46 /** 47 * Enterprise configuration details for Wi-Fi. Stores details about the EAP method 48 * and any associated credentials. 49 */ 50 public class WifiEnterpriseConfig implements Parcelable { 51 52 /** Key prefix for WAPI AS certificates. */ 53 public static final String WAPI_AS_CERTIFICATE = "WAPIAS_"; 54 55 /** Key prefix for WAPI user certificates. */ 56 public static final String WAPI_USER_CERTIFICATE = "WAPIUSR_"; 57 58 /** 59 * Intent extra: name for WAPI AS certificates 60 */ 61 public static final String EXTRA_WAPI_AS_CERTIFICATE_NAME = 62 "android.net.wifi.extra.WAPI_AS_CERTIFICATE_NAME"; 63 64 /** 65 * Intent extra: data for WAPI AS certificates 66 */ 67 public static final String EXTRA_WAPI_AS_CERTIFICATE_DATA = 68 "android.net.wifi.extra.WAPI_AS_CERTIFICATE_DATA"; 69 70 /** 71 * Intent extra: name for WAPI USER certificates 72 */ 73 public static final String EXTRA_WAPI_USER_CERTIFICATE_NAME = 74 "android.net.wifi.extra.WAPI_USER_CERTIFICATE_NAME"; 75 76 /** 77 * Intent extra: data for WAPI USER certificates 78 */ 79 public static final String EXTRA_WAPI_USER_CERTIFICATE_DATA = 80 "android.net.wifi.extra.WAPI_USER_CERTIFICATE_DATA"; 81 82 /** @hide */ 83 public static final String EMPTY_VALUE = "NULL"; 84 /** @hide */ 85 public static final String EAP_KEY = "eap"; 86 /** @hide */ 87 public static final String PHASE2_KEY = "phase2"; 88 /** @hide */ 89 public static final String IDENTITY_KEY = "identity"; 90 /** @hide */ 91 public static final String ANON_IDENTITY_KEY = "anonymous_identity"; 92 /** @hide */ 93 public static final String PASSWORD_KEY = "password"; 94 /** @hide */ 95 public static final String SUBJECT_MATCH_KEY = "subject_match"; 96 /** @hide */ 97 public static final String ALTSUBJECT_MATCH_KEY = "altsubject_match"; 98 /** @hide */ 99 public static final String DOM_SUFFIX_MATCH_KEY = "domain_suffix_match"; 100 /** @hide */ 101 public static final String OPP_KEY_CACHING = "proactive_key_caching"; 102 /** @hide */ 103 public static final String EAP_ERP = "eap_erp"; 104 /** @hide */ 105 public static final String OCSP = "ocsp"; 106 /** @hide */ 107 public static final String DECORATED_IDENTITY_PREFIX_KEY = "decorated_username_prefix"; 108 109 /** 110 * String representing the keystore OpenSSL ENGINE's ID. 111 * @hide 112 */ 113 public static final String ENGINE_ID_KEYSTORE = "keystore"; 114 115 /** 116 * String representing the keystore URI used for wpa_supplicant. 117 * @hide 118 */ 119 public static final String KEYSTORE_URI = "keystore://"; 120 121 /** 122 * String representing the keystore URI used for wpa_supplicant, 123 * Unlike #KEYSTORE_URI, this supports a list of space-delimited aliases 124 * @hide 125 */ 126 public static final String KEYSTORES_URI = "keystores://"; 127 128 /** 129 * String representing a SHA-256 certificate hash used for wpa_supplicant. 130 */ 131 private static final String CERT_HASH_PREFIX = "hash://server/sha256/"; 132 133 /** 134 * String to set the engine value to when it should be enabled. 135 * @hide 136 */ 137 public static final String ENGINE_ENABLE = "1"; 138 139 /** 140 * String to set the engine value to when it should be disabled. 141 * @hide 142 */ 143 public static final String ENGINE_DISABLE = "0"; 144 145 /** 146 * Key prefix for CA certificates. 147 * Note: copied from {@link android.security.Credentials#CA_CERTIFICATE} since it is @hide. 148 */ 149 private static final String CA_CERTIFICATE = "CACERT_"; 150 /** 151 * Key prefix for user certificates. 152 * Note: copied from {@link android.security.Credentials#USER_CERTIFICATE} since it is @hide. 153 */ 154 private static final String USER_CERTIFICATE = "USRCERT_"; 155 /** 156 * Key prefix for user private and secret keys. 157 * Note: copied from {@link android.security.Credentials#USER_PRIVATE_KEY} since it is @hide. 158 */ 159 private static final String USER_PRIVATE_KEY = "USRPKEY_"; 160 161 /** @hide */ 162 public static final String CA_CERT_PREFIX = KEYSTORE_URI + CA_CERTIFICATE; 163 /** @hide */ 164 public static final String CLIENT_CERT_PREFIX = KEYSTORE_URI + USER_CERTIFICATE; 165 /** @hide */ 166 public static final String CLIENT_CERT_KEY = "client_cert"; 167 /** @hide */ 168 public static final String CA_CERT_KEY = "ca_cert"; 169 /** @hide */ 170 public static final String CA_PATH_KEY = "ca_path"; 171 /** @hide */ 172 public static final String ENGINE_KEY = "engine"; 173 /** @hide */ 174 public static final String ENGINE_ID_KEY = "engine_id"; 175 /** @hide */ 176 public static final String PRIVATE_KEY_ID_KEY = "key_id"; 177 /** @hide */ 178 public static final String REALM_KEY = "realm"; 179 /** @hide */ 180 public static final String PLMN_KEY = "plmn"; 181 /** @hide */ 182 public static final String CA_CERT_ALIAS_DELIMITER = " "; 183 /** @hide */ 184 public static final String WAPI_CERT_SUITE_KEY = "wapi_cert_suite"; 185 186 /** 187 * Do not use OCSP stapling (TLS certificate status extension) 188 * @hide 189 */ 190 @SystemApi 191 public static final int OCSP_NONE = 0; 192 193 /** 194 * Try to use OCSP stapling, but not require response 195 * @hide 196 */ 197 @SystemApi 198 public static final int OCSP_REQUEST_CERT_STATUS = 1; 199 200 /** 201 * Require valid OCSP stapling response 202 * @hide 203 */ 204 @SystemApi 205 public static final int OCSP_REQUIRE_CERT_STATUS = 2; 206 207 /** 208 * Require valid OCSP stapling response for all not-trusted certificates in the server 209 * certificate chain. 210 * @apiNote This option is not supported by most SSL libraries and should not be used. 211 * Specifying this option will most likely cause connection failures. 212 * @hide 213 */ 214 @SystemApi 215 public static final int OCSP_REQUIRE_ALL_NON_TRUSTED_CERTS_STATUS = 3; 216 217 /** @hide */ 218 @IntDef(prefix = {"OCSP_"}, value = { 219 OCSP_NONE, 220 OCSP_REQUEST_CERT_STATUS, 221 OCSP_REQUIRE_CERT_STATUS, 222 OCSP_REQUIRE_ALL_NON_TRUSTED_CERTS_STATUS 223 }) 224 @Retention(RetentionPolicy.SOURCE) 225 public @interface Ocsp {} 226 227 /** 228 * Whether to use/require OCSP (Online Certificate Status Protocol) to check server certificate. 229 * @hide 230 */ 231 private @Ocsp int mOcsp = OCSP_NONE; 232 233 // Fields to copy verbatim from wpa_supplicant. 234 private static final String[] SUPPLICANT_CONFIG_KEYS = new String[] { 235 IDENTITY_KEY, 236 ANON_IDENTITY_KEY, 237 PASSWORD_KEY, 238 CLIENT_CERT_KEY, 239 CA_CERT_KEY, 240 SUBJECT_MATCH_KEY, 241 ENGINE_KEY, 242 ENGINE_ID_KEY, 243 PRIVATE_KEY_ID_KEY, 244 ALTSUBJECT_MATCH_KEY, 245 DOM_SUFFIX_MATCH_KEY, 246 CA_PATH_KEY 247 }; 248 249 /** 250 * Fields that have unquoted values in {@link #mFields}. 251 */ 252 private static final List<String> UNQUOTED_KEYS = Arrays.asList(ENGINE_KEY, OPP_KEY_CACHING, 253 EAP_ERP); 254 255 @UnsupportedAppUsage 256 private HashMap<String, String> mFields = new HashMap<String, String>(); 257 private X509Certificate[] mCaCerts; 258 private PrivateKey mClientPrivateKey; 259 private X509Certificate[] mClientCertificateChain; 260 private int mEapMethod = Eap.NONE; 261 private int mPhase2Method = Phase2.NONE; 262 private boolean mIsAppInstalledDeviceKeyAndCert = false; 263 private boolean mIsAppInstalledCaCert = false; 264 private String mKeyChainAlias; 265 private boolean mIsTrustOnFirstUseEnabled = false; 266 private boolean mUserApproveNoCaCert = false; 267 268 // Not included in parceling, hashing, or equality because it is an internal, temporary value 269 // which is valid only during an actual connection to a Passpoint network with an RCOI-based 270 // subscription. 271 private long mSelectedRcoi = 0; 272 273 private static final String TAG = "WifiEnterpriseConfig"; 274 WifiEnterpriseConfig()275 public WifiEnterpriseConfig() { 276 // Do not set defaults so that the enterprise fields that are not changed 277 // by API are not changed underneath 278 // This is essential because an app may not have all fields like password 279 // available. It allows modification of subset of fields. 280 281 } 282 283 /** 284 * Copy over the contents of the source WifiEnterpriseConfig object over to this object. 285 * 286 * @param source Source WifiEnterpriseConfig object. 287 * @param ignoreMaskedPassword Set to true to ignore masked password field, false otherwise. 288 * @param mask if |ignoreMaskedPassword| is set, check if the incoming password field is set 289 * to this value. 290 */ copyFrom(WifiEnterpriseConfig source, boolean ignoreMaskedPassword, String mask)291 private void copyFrom(WifiEnterpriseConfig source, boolean ignoreMaskedPassword, String mask) { 292 for (String key : source.mFields.keySet()) { 293 if (ignoreMaskedPassword && key.equals(PASSWORD_KEY) 294 && TextUtils.equals(source.mFields.get(key), mask)) { 295 continue; 296 } 297 mFields.put(key, source.mFields.get(key)); 298 } 299 if (source.mCaCerts != null) { 300 mCaCerts = Arrays.copyOf(source.mCaCerts, source.mCaCerts.length); 301 } else { 302 mCaCerts = null; 303 } 304 mClientPrivateKey = source.mClientPrivateKey; 305 if (source.mClientCertificateChain != null) { 306 mClientCertificateChain = Arrays.copyOf( 307 source.mClientCertificateChain, 308 source.mClientCertificateChain.length); 309 } else { 310 mClientCertificateChain = null; 311 } 312 mKeyChainAlias = source.mKeyChainAlias; 313 mEapMethod = source.mEapMethod; 314 mPhase2Method = source.mPhase2Method; 315 mIsAppInstalledDeviceKeyAndCert = source.mIsAppInstalledDeviceKeyAndCert; 316 mIsAppInstalledCaCert = source.mIsAppInstalledCaCert; 317 mOcsp = source.mOcsp; 318 mIsTrustOnFirstUseEnabled = source.mIsTrustOnFirstUseEnabled; 319 mUserApproveNoCaCert = source.mUserApproveNoCaCert; 320 mSelectedRcoi = source.mSelectedRcoi; 321 } 322 323 /** 324 * Copy constructor. 325 * This copies over all the fields verbatim (does not ignore masked password fields). 326 * 327 * @param source Source WifiEnterpriseConfig object. 328 */ WifiEnterpriseConfig(WifiEnterpriseConfig source)329 public WifiEnterpriseConfig(WifiEnterpriseConfig source) { 330 copyFrom(source, false, ""); 331 } 332 333 /** 334 * Copy fields from the provided external WifiEnterpriseConfig. 335 * This is needed to handle the WifiEnterpriseConfig objects which were sent by apps with the 336 * password field masked. 337 * 338 * @param externalConfig External WifiEnterpriseConfig object. 339 * @param mask String mask to compare against. 340 * @hide 341 */ copyFromExternal(WifiEnterpriseConfig externalConfig, String mask)342 public void copyFromExternal(WifiEnterpriseConfig externalConfig, String mask) { 343 copyFrom(externalConfig, true, convertToQuotedString(mask)); 344 } 345 346 @Override describeContents()347 public int describeContents() { 348 return 0; 349 } 350 351 @Override writeToParcel(Parcel dest, int flags)352 public void writeToParcel(Parcel dest, int flags) { 353 dest.writeInt(mFields.size()); 354 for (Map.Entry<String, String> entry : mFields.entrySet()) { 355 dest.writeString(entry.getKey()); 356 dest.writeString(entry.getValue()); 357 } 358 359 dest.writeInt(mEapMethod); 360 dest.writeInt(mPhase2Method); 361 ParcelUtil.writeCertificates(dest, mCaCerts); 362 ParcelUtil.writePrivateKey(dest, mClientPrivateKey); 363 ParcelUtil.writeCertificates(dest, mClientCertificateChain); 364 dest.writeString(mKeyChainAlias); 365 dest.writeBoolean(mIsAppInstalledDeviceKeyAndCert); 366 dest.writeBoolean(mIsAppInstalledCaCert); 367 dest.writeInt(mOcsp); 368 dest.writeBoolean(mIsTrustOnFirstUseEnabled); 369 dest.writeBoolean(mUserApproveNoCaCert); 370 } 371 372 public static final @android.annotation.NonNull Creator<WifiEnterpriseConfig> CREATOR = 373 new Creator<WifiEnterpriseConfig>() { 374 @Override 375 public WifiEnterpriseConfig createFromParcel(Parcel in) { 376 WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig(); 377 int count = in.readInt(); 378 for (int i = 0; i < count; i++) { 379 String key = in.readString(); 380 String value = in.readString(); 381 enterpriseConfig.mFields.put(key, value); 382 } 383 384 enterpriseConfig.mEapMethod = in.readInt(); 385 enterpriseConfig.mPhase2Method = in.readInt(); 386 enterpriseConfig.mCaCerts = ParcelUtil.readCertificates(in); 387 enterpriseConfig.mClientPrivateKey = ParcelUtil.readPrivateKey(in); 388 enterpriseConfig.mClientCertificateChain = ParcelUtil.readCertificates(in); 389 enterpriseConfig.mKeyChainAlias = in.readString(); 390 enterpriseConfig.mIsAppInstalledDeviceKeyAndCert = in.readBoolean(); 391 enterpriseConfig.mIsAppInstalledCaCert = in.readBoolean(); 392 enterpriseConfig.mOcsp = in.readInt(); 393 enterpriseConfig.mIsTrustOnFirstUseEnabled = in.readBoolean(); 394 enterpriseConfig.mUserApproveNoCaCert = in.readBoolean(); 395 return enterpriseConfig; 396 } 397 398 @Override 399 public WifiEnterpriseConfig[] newArray(int size) { 400 return new WifiEnterpriseConfig[size]; 401 } 402 }; 403 404 /** The Extensible Authentication Protocol method used */ 405 public static final class Eap { 406 /** No EAP method used. Represents an empty config */ 407 public static final int NONE = -1; 408 /** Protected EAP */ 409 public static final int PEAP = 0; 410 /** EAP-Transport Layer Security */ 411 public static final int TLS = 1; 412 /** EAP-Tunneled Transport Layer Security */ 413 public static final int TTLS = 2; 414 /** EAP-Password */ 415 public static final int PWD = 3; 416 /** EAP-Subscriber Identity Module [RFC-4186] */ 417 public static final int SIM = 4; 418 /** EAP-Authentication and Key Agreement [RFC-4187] */ 419 public static final int AKA = 5; 420 /** EAP-Authentication and Key Agreement Prime [RFC-5448] */ 421 public static final int AKA_PRIME = 6; 422 /** Hotspot 2.0 r2 OSEN */ 423 public static final int UNAUTH_TLS = 7; 424 /** WAPI Certificate */ 425 public static final int WAPI_CERT = 8; 426 /** @hide */ 427 public static final String[] strings = 428 { "PEAP", "TLS", "TTLS", "PWD", "SIM", "AKA", "AKA'", "WFA-UNAUTH-TLS", 429 "WAPI_CERT" }; 430 431 /** Prevent initialization */ Eap()432 private Eap() {} 433 } 434 435 /** The inner authentication method used */ 436 public static final class Phase2 { 437 public static final int NONE = 0; 438 /** Password Authentication Protocol */ 439 public static final int PAP = 1; 440 /** Microsoft Challenge Handshake Authentication Protocol */ 441 public static final int MSCHAP = 2; 442 /** Microsoft Challenge Handshake Authentication Protocol v2 */ 443 public static final int MSCHAPV2 = 3; 444 /** Generic Token Card */ 445 public static final int GTC = 4; 446 /** EAP-Subscriber Identity Module [RFC-4186] */ 447 public static final int SIM = 5; 448 /** EAP-Authentication and Key Agreement [RFC-4187] */ 449 public static final int AKA = 6; 450 /** EAP-Authentication and Key Agreement Prime [RFC-5448] */ 451 public static final int AKA_PRIME = 7; 452 private static final String AUTH_PREFIX = "auth="; 453 private static final String AUTHEAP_PREFIX = "autheap="; 454 /** @hide */ 455 public static final String[] strings = {EMPTY_VALUE, "PAP", "MSCHAP", 456 "MSCHAPV2", "GTC", "SIM", "AKA", "AKA'" }; 457 458 /** Prevent initialization */ Phase2()459 private Phase2() {} 460 } 461 462 // Loader and saver interfaces for exchanging data with wpa_supplicant. 463 // TODO: Decouple this object (which is just a placeholder of the configuration) 464 // from the implementation that knows what wpa_supplicant wants. 465 /** 466 * Interface used for retrieving supplicant configuration from WifiEnterpriseConfig 467 * @hide 468 */ 469 public interface SupplicantSaver { 470 /** 471 * Set a value within wpa_supplicant configuration 472 * @param key index to set within wpa_supplciant 473 * @param value the value for the key 474 * @return true if successful; false otherwise 475 */ saveValue(String key, String value)476 boolean saveValue(String key, String value); 477 } 478 479 /** 480 * Interface used for populating a WifiEnterpriseConfig from supplicant configuration 481 * @hide 482 */ 483 public interface SupplicantLoader { 484 /** 485 * Returns a value within wpa_supplicant configuration 486 * @param key index to set within wpa_supplciant 487 * @return string value if successful; null otherwise 488 */ loadValue(String key)489 String loadValue(String key); 490 } 491 492 /** 493 * Internal use only; supply field values to wpa_supplicant config. The configuration 494 * process aborts on the first failed call on {@code saver}. 495 * @param saver proxy for setting configuration in wpa_supplciant 496 * @return whether the save succeeded on all attempts 497 * @hide 498 */ saveToSupplicant(SupplicantSaver saver)499 public boolean saveToSupplicant(SupplicantSaver saver) { 500 if (!isEapMethodValid()) { 501 return false; 502 } 503 504 // wpa_supplicant can update the anonymous identity for these kinds of networks after 505 // framework reads them, so make sure the framework doesn't try to overwrite them. 506 boolean shouldNotWriteAnonIdentity = mEapMethod == WifiEnterpriseConfig.Eap.SIM 507 || mEapMethod == WifiEnterpriseConfig.Eap.AKA 508 || mEapMethod == WifiEnterpriseConfig.Eap.AKA_PRIME; 509 for (String key : mFields.keySet()) { 510 if (shouldNotWriteAnonIdentity && ANON_IDENTITY_KEY.equals(key)) { 511 continue; 512 } 513 if (!saver.saveValue(key, mFields.get(key))) { 514 return false; 515 } 516 } 517 518 if (!saver.saveValue(EAP_KEY, Eap.strings[mEapMethod])) { 519 return false; 520 } 521 522 if (mEapMethod != Eap.TLS && mEapMethod != Eap.UNAUTH_TLS && mPhase2Method != Phase2.NONE) { 523 boolean is_autheap = mEapMethod == Eap.TTLS && mPhase2Method == Phase2.GTC; 524 String prefix = is_autheap ? Phase2.AUTHEAP_PREFIX : Phase2.AUTH_PREFIX; 525 String value = convertToQuotedString(prefix + Phase2.strings[mPhase2Method]); 526 return saver.saveValue(PHASE2_KEY, value); 527 } else if (mPhase2Method == Phase2.NONE) { 528 // By default, send a null phase 2 to clear old configuration values. 529 return saver.saveValue(PHASE2_KEY, null); 530 } else { 531 Log.e(TAG, "WiFi enterprise configuration is invalid as it supplies a " 532 + "phase 2 method but the phase1 method does not support it."); 533 return false; 534 } 535 } 536 537 /** 538 * Internal use only; retrieve configuration from wpa_supplicant config. 539 * @param loader proxy for retrieving configuration keys from wpa_supplicant 540 * @hide 541 */ loadFromSupplicant(SupplicantLoader loader)542 public void loadFromSupplicant(SupplicantLoader loader) { 543 for (String key : SUPPLICANT_CONFIG_KEYS) { 544 String value = loader.loadValue(key); 545 if (value == null) { 546 mFields.put(key, EMPTY_VALUE); 547 } else { 548 mFields.put(key, value); 549 } 550 } 551 String eapMethod = loader.loadValue(EAP_KEY); 552 mEapMethod = getStringIndex(Eap.strings, eapMethod, Eap.NONE); 553 554 String phase2Method = removeDoubleQuotes(loader.loadValue(PHASE2_KEY)); 555 // Remove "auth=" or "autheap=" prefix. 556 if (phase2Method.startsWith(Phase2.AUTH_PREFIX)) { 557 phase2Method = phase2Method.substring(Phase2.AUTH_PREFIX.length()); 558 } else if (phase2Method.startsWith(Phase2.AUTHEAP_PREFIX)) { 559 phase2Method = phase2Method.substring(Phase2.AUTHEAP_PREFIX.length()); 560 } 561 mPhase2Method = getStringIndex(Phase2.strings, phase2Method, Phase2.NONE); 562 } 563 564 /** 565 * Set the EAP authentication method. 566 * @param eapMethod is one of {@link Eap}, except for {@link Eap#NONE} 567 * @throws IllegalArgumentException on an invalid eap method 568 */ setEapMethod(int eapMethod)569 public void setEapMethod(int eapMethod) { 570 switch (eapMethod) { 571 /** Valid methods */ 572 case Eap.WAPI_CERT: 573 mEapMethod = eapMethod; 574 setPhase2Method(Phase2.NONE); 575 break; 576 case Eap.TLS: 577 case Eap.UNAUTH_TLS: 578 setPhase2Method(Phase2.NONE); 579 /* fall through */ 580 case Eap.PEAP: 581 case Eap.PWD: 582 case Eap.TTLS: 583 case Eap.SIM: 584 case Eap.AKA: 585 case Eap.AKA_PRIME: 586 mEapMethod = eapMethod; 587 setFieldValue(OPP_KEY_CACHING, "1"); 588 break; 589 default: 590 throw new IllegalArgumentException("Unknown EAP method"); 591 } 592 } 593 594 /** 595 * Get the eap method. 596 * @return eap method configured 597 */ getEapMethod()598 public int getEapMethod() { 599 return mEapMethod; 600 } 601 602 /** 603 * Set Phase 2 authentication method. Sets the inner authentication method to be used in 604 * phase 2 after setting up a secure channel 605 * @param phase2Method is the inner authentication method and can be one of {@link Phase2} 606 * @throws IllegalArgumentException on an invalid phase2 method 607 */ setPhase2Method(int phase2Method)608 public void setPhase2Method(int phase2Method) { 609 switch (phase2Method) { 610 case Phase2.NONE: 611 case Phase2.PAP: 612 case Phase2.MSCHAP: 613 case Phase2.MSCHAPV2: 614 case Phase2.GTC: 615 case Phase2.SIM: 616 case Phase2.AKA: 617 case Phase2.AKA_PRIME: 618 mPhase2Method = phase2Method; 619 break; 620 default: 621 throw new IllegalArgumentException("Unknown Phase 2 method"); 622 } 623 } 624 625 /** 626 * Get the phase 2 authentication method. 627 * @return a phase 2 method defined at {@link Phase2} 628 * */ getPhase2Method()629 public int getPhase2Method() { 630 return mPhase2Method; 631 } 632 633 /** 634 * Set the identity 635 * @param identity 636 */ setIdentity(String identity)637 public void setIdentity(String identity) { 638 setFieldValue(IDENTITY_KEY, identity, ""); 639 } 640 641 /** 642 * Get the identity 643 * @return the identity 644 */ getIdentity()645 public String getIdentity() { 646 return getFieldValue(IDENTITY_KEY); 647 } 648 649 /** 650 * Set anonymous identity. This is used as the unencrypted identity with 651 * certain EAP types 652 * @param anonymousIdentity the anonymous identity 653 */ setAnonymousIdentity(String anonymousIdentity)654 public void setAnonymousIdentity(String anonymousIdentity) { 655 setFieldValue(ANON_IDENTITY_KEY, anonymousIdentity); 656 } 657 658 /** 659 * Get the anonymous identity 660 * @return anonymous identity 661 */ getAnonymousIdentity()662 public String getAnonymousIdentity() { 663 return getFieldValue(ANON_IDENTITY_KEY); 664 } 665 666 /** 667 * Set the password. 668 * @param password the password 669 */ setPassword(String password)670 public void setPassword(String password) { 671 setFieldValue(PASSWORD_KEY, password); 672 } 673 674 /** 675 * Get the password. 676 * 677 * Returns locally set password value. For networks fetched from 678 * framework, returns "*". 679 */ getPassword()680 public String getPassword() { 681 return getFieldValue(PASSWORD_KEY); 682 } 683 684 /** 685 * Encode a CA certificate alias so it does not contain illegal character. 686 * @hide 687 */ encodeCaCertificateAlias(String alias)688 public static String encodeCaCertificateAlias(String alias) { 689 byte[] bytes = alias.getBytes(StandardCharsets.UTF_8); 690 StringBuilder sb = new StringBuilder(bytes.length * 2); 691 for (byte o : bytes) { 692 sb.append(String.format("%02x", o & 0xFF)); 693 } 694 return sb.toString(); 695 } 696 697 /** 698 * Decode a previously-encoded CA certificate alias. 699 * @hide 700 */ decodeCaCertificateAlias(String alias)701 public static String decodeCaCertificateAlias(String alias) { 702 byte[] data = new byte[alias.length() >> 1]; 703 for (int n = 0, position = 0; n < alias.length(); n += 2, position++) { 704 data[position] = (byte) Integer.parseInt(alias.substring(n, n + 2), 16); 705 } 706 try { 707 return new String(data, StandardCharsets.UTF_8); 708 } catch (NumberFormatException e) { 709 e.printStackTrace(); 710 return alias; 711 } 712 } 713 714 /** 715 * Set a server certificate hash instead of a CA certificate for a TOFU connection 716 * 717 * @param certHash Server certificate hash to match against in subsequent connections 718 * @hide 719 */ setServerCertificateHash(String certHash)720 public void setServerCertificateHash(String certHash) { 721 setFieldValue(CA_CERT_KEY, certHash, CERT_HASH_PREFIX); 722 } 723 724 /** 725 * Set CA certificate alias. 726 * 727 * <p> See the {@link android.security.KeyChain} for details on installing or choosing 728 * a certificate 729 * </p> 730 * @param alias identifies the certificate 731 * @hide 732 */ 733 @UnsupportedAppUsage setCaCertificateAlias(String alias)734 public void setCaCertificateAlias(String alias) { 735 setFieldValue(CA_CERT_KEY, alias, CA_CERT_PREFIX); 736 } 737 738 /** 739 * Set CA certificate aliases. When creating installing the corresponding certificate to 740 * the keystore, please use alias encoded by {@link #encodeCaCertificateAlias(String)}. 741 * 742 * <p> See the {@link android.security.KeyChain} for details on installing or choosing 743 * a certificate. 744 * </p> 745 * @param aliases identifies the certificate. Can be null to indicate the absence of a 746 * certificate. 747 * @hide 748 */ 749 @SystemApi setCaCertificateAliases(@ullable String[] aliases)750 public void setCaCertificateAliases(@Nullable String[] aliases) { 751 if (aliases == null) { 752 setFieldValue(CA_CERT_KEY, null, CA_CERT_PREFIX); 753 } else if (aliases.length == 1) { 754 // Backwards compatibility: use the original cert prefix if setting only one alias. 755 setCaCertificateAlias(aliases[0]); 756 } else { 757 // Use KEYSTORES_URI which supports multiple aliases. 758 StringBuilder sb = new StringBuilder(); 759 for (int i = 0; i < aliases.length; i++) { 760 if (i > 0) { 761 sb.append(CA_CERT_ALIAS_DELIMITER); 762 } 763 sb.append(encodeCaCertificateAlias(CA_CERTIFICATE + aliases[i])); 764 } 765 setFieldValue(CA_CERT_KEY, sb.toString(), KEYSTORES_URI); 766 } 767 } 768 769 /** 770 * Indicates whether or not this enterprise config has a CA certificate configured. 771 */ hasCaCertificate()772 public boolean hasCaCertificate() { 773 if (getCaCertificateAliases() != null) return true; 774 if (getCaCertificates() != null) return true; 775 if (!TextUtils.isEmpty(getCaPath())) return true; 776 return false; 777 } 778 779 /** 780 * Get CA certificate alias 781 * @return alias to the CA certificate 782 * @hide 783 */ 784 @UnsupportedAppUsage getCaCertificateAlias()785 public String getCaCertificateAlias() { 786 return getFieldValue(CA_CERT_KEY, CA_CERT_PREFIX); 787 } 788 789 /** 790 * Get CA certificate aliases. 791 * @return alias to the CA certificate, or null if unset. 792 * @hide 793 */ 794 @Nullable 795 @SystemApi getCaCertificateAliases()796 public String[] getCaCertificateAliases() { 797 String value = getFieldValue(CA_CERT_KEY); 798 if (value.startsWith(CA_CERT_PREFIX)) { 799 // Backwards compatibility: parse the original alias prefix. 800 return new String[] {getFieldValue(CA_CERT_KEY, CA_CERT_PREFIX)}; 801 } else if (value.startsWith(KEYSTORES_URI)) { 802 String values = value.substring(KEYSTORES_URI.length()); 803 804 String[] aliases = TextUtils.split(values, CA_CERT_ALIAS_DELIMITER); 805 for (int i = 0; i < aliases.length; i++) { 806 aliases[i] = decodeCaCertificateAlias(aliases[i]); 807 if (aliases[i].startsWith(CA_CERTIFICATE)) { 808 aliases[i] = aliases[i].substring(CA_CERTIFICATE.length()); 809 } 810 } 811 return aliases.length != 0 ? aliases : null; 812 } else { 813 return TextUtils.isEmpty(value) ? null : new String[] {value}; 814 } 815 } 816 817 /** 818 * Specify a X.509 certificate that identifies the server. 819 * 820 * <p>A default name is automatically assigned to the certificate and used 821 * with this configuration. The framework takes care of installing the 822 * certificate when the config is saved and removing the certificate when 823 * the config is removed. 824 * 825 * Note: If no certificate is set for an Enterprise configuration, either by not calling this 826 * API (or the {@link #setCaCertificates(X509Certificate[])}, or by calling it with null, then 827 * the server certificate validation is skipped - which means that the connection is not secure. 828 * 829 * @param cert X.509 CA certificate 830 * @throws IllegalArgumentException if not a CA certificate 831 */ setCaCertificate(@ullable X509Certificate cert)832 public void setCaCertificate(@Nullable X509Certificate cert) { 833 if (cert != null) { 834 if (cert.getBasicConstraints() >= 0) { 835 mIsAppInstalledCaCert = true; 836 mCaCerts = new X509Certificate[] {cert}; 837 } else { 838 mCaCerts = null; 839 throw new IllegalArgumentException("Not a CA certificate"); 840 } 841 } else { 842 mCaCerts = null; 843 } 844 } 845 846 /** 847 * Specify a X.509 certificate that identifies the server. 848 * 849 * This hidden API allows setting self-signed certificate for Trust on First Use. 850 * 851 * @param cert X.509 CA certificate 852 * @throws IllegalArgumentException if Trust on First Use is not enabled. 853 * @hide 854 */ setCaCertificateForTrustOnFirstUse(@ullable X509Certificate cert)855 public void setCaCertificateForTrustOnFirstUse(@Nullable X509Certificate cert) { 856 if (cert != null) { 857 if (isTrustOnFirstUseEnabled()) { 858 mIsAppInstalledCaCert = true; 859 mCaCerts = new X509Certificate[] {cert}; 860 } else { 861 mCaCerts = null; 862 throw new IllegalArgumentException("Trust on First Use is not enabled."); 863 } 864 } else { 865 mCaCerts = null; 866 } 867 } 868 869 /** 870 * Get CA certificate. If multiple CA certificates are configured previously, 871 * return the first one. 872 * @return X.509 CA certificate 873 */ getCaCertificate()874 @Nullable public X509Certificate getCaCertificate() { 875 if (mCaCerts != null && mCaCerts.length > 0) { 876 return mCaCerts[0]; 877 } else { 878 return null; 879 } 880 } 881 882 /** 883 * Specify a list of X.509 certificates that identifies the server. The validation 884 * passes if the CA of server certificate matches one of the given certificates. 885 886 * <p>Default names are automatically assigned to the certificates and used 887 * with this configuration. The framework takes care of installing the 888 * certificates when the config is saved and removing the certificates when 889 * the config is removed. 890 * 891 * Note: If no certificates are set for an Enterprise configuration, either by not calling this 892 * API (or the {@link #setCaCertificate(X509Certificate)}, or by calling it with null, then the 893 * server certificate validation is skipped - which means that the 894 * connection is not secure. 895 * 896 * @param certs X.509 CA certificates 897 * @throws IllegalArgumentException if any of the provided certificates is 898 * not a CA certificate 899 */ setCaCertificates(@ullable X509Certificate[] certs)900 public void setCaCertificates(@Nullable X509Certificate[] certs) { 901 if (certs != null) { 902 X509Certificate[] newCerts = new X509Certificate[certs.length]; 903 for (int i = 0; i < certs.length; i++) { 904 if (certs[i].getBasicConstraints() >= 0) { 905 newCerts[i] = certs[i]; 906 } else { 907 mCaCerts = null; 908 throw new IllegalArgumentException("Not a CA certificate"); 909 } 910 } 911 mCaCerts = newCerts; 912 mIsAppInstalledCaCert = true; 913 } else { 914 mCaCerts = null; 915 } 916 } 917 918 /** 919 * Get CA certificates. 920 */ getCaCertificates()921 @Nullable public X509Certificate[] getCaCertificates() { 922 if (mCaCerts != null && mCaCerts.length > 0) { 923 return mCaCerts; 924 } else { 925 return null; 926 } 927 } 928 929 /** 930 * @hide 931 */ resetCaCertificate()932 public void resetCaCertificate() { 933 mCaCerts = null; 934 } 935 936 /** 937 * Set the ca_path directive on wpa_supplicant. 938 * 939 * From wpa_supplicant documentation: 940 * 941 * Directory path for CA certificate files (PEM). This path may contain 942 * multiple CA certificates in OpenSSL format. Common use for this is to 943 * point to system trusted CA list which is often installed into directory 944 * like /etc/ssl/certs. If configured, these certificates are added to the 945 * list of trusted CAs. ca_cert may also be included in that case, but it is 946 * not required. 947 * 948 * Note: If no certificate path is set for an Enterprise configuration, either by not calling 949 * this API, or by calling it with null, and no certificate is set by 950 * {@link #setCaCertificate(X509Certificate)} or {@link #setCaCertificates(X509Certificate[])}, 951 * then the server certificate validation is skipped - which means that the connection is not 952 * secure. 953 * 954 * @param path The path for CA certificate files, or empty string to clear. 955 * @hide 956 */ 957 @SystemApi setCaPath(@onNull String path)958 public void setCaPath(@NonNull String path) { 959 setFieldValue(CA_PATH_KEY, path); 960 } 961 962 /** 963 * Get the ca_path directive from wpa_supplicant. 964 * @return The path for CA certificate files, or an empty string if unset. 965 * @hide 966 */ 967 @NonNull 968 @SystemApi getCaPath()969 public String getCaPath() { 970 return getFieldValue(CA_PATH_KEY); 971 } 972 973 /** 974 * Set Client certificate alias. 975 * 976 * <p> See the {@link android.security.KeyChain} for details on installing or choosing 977 * a certificate 978 * </p> 979 * @param alias identifies the certificate, or empty string to clear. 980 * @hide 981 */ 982 @SystemApi setClientCertificateAlias(@onNull String alias)983 public void setClientCertificateAlias(@NonNull String alias) { 984 setFieldValue(CLIENT_CERT_KEY, alias, CLIENT_CERT_PREFIX); 985 setFieldValue(PRIVATE_KEY_ID_KEY, alias, USER_PRIVATE_KEY); 986 // Also, set engine parameters 987 if (TextUtils.isEmpty(alias)) { 988 setFieldValue(ENGINE_KEY, ENGINE_DISABLE); 989 setFieldValue(ENGINE_ID_KEY, ""); 990 } else { 991 setFieldValue(ENGINE_KEY, ENGINE_ENABLE); 992 setFieldValue(ENGINE_ID_KEY, ENGINE_ID_KEYSTORE); 993 } 994 } 995 996 /** 997 * Get client certificate alias. 998 * @return alias to the client certificate, or an empty string if unset. 999 * @hide 1000 */ 1001 @NonNull 1002 @SystemApi getClientCertificateAlias()1003 public String getClientCertificateAlias() { 1004 return getFieldValue(CLIENT_CERT_KEY, CLIENT_CERT_PREFIX); 1005 } 1006 1007 /** 1008 * Specify a private key and client certificate for client authorization. 1009 * 1010 * <p>A default name is automatically assigned to the key entry and used 1011 * with this configuration. The framework takes care of installing the 1012 * key entry when the config is saved and removing the key entry when 1013 * the config is removed. 1014 1015 * @param privateKey a PrivateKey instance for the end certificate. 1016 * @param clientCertificate an X509Certificate representing the end certificate. 1017 * @throws IllegalArgumentException for an invalid key or certificate. 1018 */ setClientKeyEntry(PrivateKey privateKey, X509Certificate clientCertificate)1019 public void setClientKeyEntry(PrivateKey privateKey, X509Certificate clientCertificate) { 1020 X509Certificate[] clientCertificates = null; 1021 if (clientCertificate != null) { 1022 clientCertificates = new X509Certificate[] {clientCertificate}; 1023 } 1024 setClientKeyEntryWithCertificateChain(privateKey, clientCertificates); 1025 } 1026 1027 /** 1028 * Specify a private key and client certificate chain for client authorization. 1029 * 1030 * <p>A default name is automatically assigned to the key entry and used 1031 * with this configuration. The framework takes care of installing the 1032 * key entry when the config is saved and removing the key entry when 1033 * the config is removed. 1034 * 1035 * @param privateKey a PrivateKey instance for the end certificate. 1036 * @param clientCertificateChain an array of X509Certificate instances which starts with 1037 * end certificate and continues with additional CA certificates necessary to 1038 * link the end certificate with some root certificate known by the authenticator. 1039 * @throws IllegalArgumentException for an invalid key or certificate. 1040 */ setClientKeyEntryWithCertificateChain(PrivateKey privateKey, X509Certificate[] clientCertificateChain)1041 public void setClientKeyEntryWithCertificateChain(PrivateKey privateKey, 1042 X509Certificate[] clientCertificateChain) { 1043 X509Certificate[] newCerts = null; 1044 if (clientCertificateChain != null && clientCertificateChain.length > 0) { 1045 // We validate that this is a well formed chain that starts 1046 // with an end-certificate and is followed by CA certificates. 1047 // We don't validate that each following certificate verifies 1048 // the previous. https://en.wikipedia.org/wiki/Chain_of_trust 1049 // 1050 // Basic constraints is an X.509 extension type that defines 1051 // whether a given certificate is allowed to sign additional 1052 // certificates and what path length restrictions may exist. 1053 // We use this to judge whether the certificate is an end 1054 // certificate or a CA certificate. 1055 // https://cryptography.io/en/latest/x509/reference/ 1056 if (clientCertificateChain[0].getBasicConstraints() != -1) { 1057 throw new IllegalArgumentException( 1058 "First certificate in the chain must be a client end certificate"); 1059 } 1060 1061 for (int i = 1; i < clientCertificateChain.length; i++) { 1062 if (clientCertificateChain[i].getBasicConstraints() == -1) { 1063 throw new IllegalArgumentException( 1064 "All certificates following the first must be CA certificates"); 1065 } 1066 } 1067 newCerts = Arrays.copyOf(clientCertificateChain, 1068 clientCertificateChain.length); 1069 1070 if (privateKey == null) { 1071 throw new IllegalArgumentException("Client cert without a private key"); 1072 } 1073 if (privateKey.getEncoded() == null) { 1074 throw new IllegalArgumentException("Private key cannot be encoded"); 1075 } 1076 } 1077 1078 mClientPrivateKey = privateKey; 1079 mClientCertificateChain = newCerts; 1080 mIsAppInstalledDeviceKeyAndCert = true; 1081 } 1082 1083 /** 1084 * Specify a key pair via KeyChain alias for client authentication. 1085 * 1086 * The alias should refer to a key pair in KeyChain that is allowed for WiFi authentication. 1087 * 1088 * @param alias key pair alias 1089 * @see android.app.admin.DevicePolicyManager#grantKeyPairToWifiAuth(String) 1090 */ 1091 @RequiresApi(Build.VERSION_CODES.S) setClientKeyPairAlias(@onNull String alias)1092 public void setClientKeyPairAlias(@NonNull String alias) { 1093 if (!SdkLevel.isAtLeastS()) { 1094 throw new UnsupportedOperationException(); 1095 } 1096 mKeyChainAlias = alias; 1097 } 1098 1099 /** 1100 * Get KeyChain alias to use for client authentication. 1101 */ 1102 @RequiresApi(Build.VERSION_CODES.S) getClientKeyPairAlias()1103 public @Nullable String getClientKeyPairAlias() { 1104 if (!SdkLevel.isAtLeastS()) { 1105 throw new UnsupportedOperationException(); 1106 } 1107 return mKeyChainAlias; 1108 } 1109 1110 /** 1111 * Get KeyChain alias to use for client authentication without SDK check. 1112 * @hide 1113 */ getClientKeyPairAliasInternal()1114 public @Nullable String getClientKeyPairAliasInternal() { 1115 return mKeyChainAlias; 1116 } 1117 1118 /** 1119 * Get client certificate 1120 * 1121 * @return X.509 client certificate 1122 */ getClientCertificate()1123 public X509Certificate getClientCertificate() { 1124 if (mClientCertificateChain != null && mClientCertificateChain.length > 0) { 1125 return mClientCertificateChain[0]; 1126 } else { 1127 return null; 1128 } 1129 } 1130 1131 /** 1132 * Get the complete client certificate chain in the same order as it was last supplied. 1133 * 1134 * <p>If the chain was last supplied by a call to 1135 * {@link #setClientKeyEntry(java.security.PrivateKey, java.security.cert.X509Certificate)} 1136 * with a non-null * certificate instance, a single-element array containing the certificate 1137 * will be * returned. If {@link #setClientKeyEntryWithCertificateChain( 1138 * java.security.PrivateKey, java.security.cert.X509Certificate[])} was last called with a 1139 * non-empty array, this array will be returned in the same order as it was supplied. 1140 * Otherwise, {@code null} will be returned. 1141 * 1142 * @return X.509 client certificates 1143 */ getClientCertificateChain()1144 @Nullable public X509Certificate[] getClientCertificateChain() { 1145 if (mClientCertificateChain != null && mClientCertificateChain.length > 0) { 1146 return mClientCertificateChain; 1147 } else { 1148 return null; 1149 } 1150 } 1151 1152 /** 1153 * @hide 1154 */ resetClientKeyEntry()1155 public void resetClientKeyEntry() { 1156 mClientPrivateKey = null; 1157 mClientCertificateChain = null; 1158 } 1159 1160 /** 1161 * Get the client private key as supplied in {@link #setClientKeyEntryWithCertificateChain}, or 1162 * null if unset. 1163 */ 1164 @Nullable getClientPrivateKey()1165 public PrivateKey getClientPrivateKey() { 1166 return mClientPrivateKey; 1167 } 1168 1169 /** 1170 * Set subject match (deprecated). This is the substring to be matched against the subject of 1171 * the authentication server certificate. 1172 * @param subjectMatch substring to be matched 1173 * @deprecated in favor of altSubjectMatch 1174 */ setSubjectMatch(String subjectMatch)1175 public void setSubjectMatch(String subjectMatch) { 1176 setFieldValue(SUBJECT_MATCH_KEY, subjectMatch); 1177 } 1178 1179 /** 1180 * Get subject match (deprecated) 1181 * @return the subject match string 1182 * @deprecated in favor of altSubjectMatch 1183 */ getSubjectMatch()1184 public String getSubjectMatch() { 1185 return getFieldValue(SUBJECT_MATCH_KEY); 1186 } 1187 1188 /** 1189 * Set alternate subject match. This is the substring to be matched against the 1190 * alternate subject of the authentication server certificate. 1191 * 1192 * Note: If no alternate subject is set for an Enterprise configuration, either by not calling 1193 * this API, or by calling it with null, or not setting domain suffix match using the 1194 * {@link #setDomainSuffixMatch(String)}, then the server certificate validation is incomplete - 1195 * which means that the connection is not secure. 1196 * 1197 * @param altSubjectMatch substring to be matched, for example 1198 * DNS:server.example.com;EMAIL:server@example.com 1199 */ setAltSubjectMatch(String altSubjectMatch)1200 public void setAltSubjectMatch(String altSubjectMatch) { 1201 setFieldValue(ALTSUBJECT_MATCH_KEY, altSubjectMatch); 1202 } 1203 1204 /** 1205 * Get alternate subject match 1206 * @return the alternate subject match string 1207 */ getAltSubjectMatch()1208 public String getAltSubjectMatch() { 1209 return getFieldValue(ALTSUBJECT_MATCH_KEY); 1210 } 1211 1212 /** 1213 * Set the domain_suffix_match directive on wpa_supplicant. This is the parameter to use 1214 * for Hotspot 2.0 defined matching of AAA server certs per WFA HS2.0 spec, section 7.3.3.2, 1215 * second paragraph. 1216 * 1217 * <p>From wpa_supplicant documentation: 1218 * <p>Constraint for server domain name. If set, this FQDN is used as a suffix match requirement 1219 * for the AAAserver certificate in SubjectAltName dNSName element(s). If a matching dNSName is 1220 * found, this constraint is met. 1221 * <p>Suffix match here means that the host/domain name is compared one label at a time starting 1222 * from the top-level domain and all the labels in domain_suffix_match shall be included in the 1223 * certificate. The certificate may include additional sub-level labels in addition to the 1224 * required labels. 1225 * <p>More than one match string can be provided by using semicolons to separate the strings 1226 * (e.g., example.org;example.com). When multiple strings are specified, a match with any one of 1227 * the values is considered a sufficient match for the certificate, i.e., the conditions are 1228 * ORed ogether. 1229 * <p>For example, domain_suffix_match=example.com would match test.example.com but would not 1230 * match test-example.com. 1231 * 1232 * Note: If no domain suffix is set for an Enterprise configuration, either by not calling this 1233 * API, or by calling it with null, or not setting alternate subject match using the 1234 * {@link #setAltSubjectMatch(String)}, then the server certificate 1235 * validation is incomplete - which means that the connection is not secure. 1236 * 1237 * @param domain The domain value 1238 */ setDomainSuffixMatch(String domain)1239 public void setDomainSuffixMatch(String domain) { 1240 setFieldValue(DOM_SUFFIX_MATCH_KEY, domain); 1241 } 1242 1243 /** 1244 * Get the domain_suffix_match value. See setDomSuffixMatch. 1245 * @return The domain value. 1246 */ getDomainSuffixMatch()1247 public String getDomainSuffixMatch() { 1248 return getFieldValue(DOM_SUFFIX_MATCH_KEY); 1249 } 1250 1251 /** 1252 * Set realm for Passpoint credential; realm identifies a set of networks where your 1253 * Passpoint credential can be used 1254 * @param realm the realm 1255 */ setRealm(String realm)1256 public void setRealm(String realm) { 1257 setFieldValue(REALM_KEY, realm); 1258 } 1259 1260 /** 1261 * Get realm for Passpoint credential; see {@link #setRealm(String)} for more information 1262 * @return the realm 1263 */ getRealm()1264 public String getRealm() { 1265 return getFieldValue(REALM_KEY); 1266 } 1267 1268 /** 1269 * Set selected RCOI for Passpoint: Indicates which RCOI was selected on a particular network 1270 * @param selectedRcoi the selected RCOI on a particular network 1271 * @hide 1272 */ setSelectedRcoi(long selectedRcoi)1273 public void setSelectedRcoi(long selectedRcoi) { 1274 mSelectedRcoi = selectedRcoi; 1275 } 1276 1277 /** 1278 * Get the selected RCOI matched for a Passpoint connection 1279 * @return the selected RCOI 1280 * @hide 1281 */ getSelectedRcoi()1282 public long getSelectedRcoi() { 1283 return mSelectedRcoi; 1284 } 1285 1286 /** 1287 * Set plmn (Public Land Mobile Network) of the provider of Passpoint credential 1288 * @param plmn the plmn value derived from mcc (mobile country code) & mnc (mobile network code) 1289 */ setPlmn(String plmn)1290 public void setPlmn(String plmn) { 1291 setFieldValue(PLMN_KEY, plmn); 1292 } 1293 1294 /** 1295 * Get plmn (Public Land Mobile Network) for Passpoint credential; see {@link #setPlmn 1296 * (String)} for more information 1297 * @return the plmn 1298 */ getPlmn()1299 public String getPlmn() { 1300 return getFieldValue(PLMN_KEY); 1301 } 1302 1303 /** See {@link WifiConfiguration#getKeyIdForCredentials} @hide */ getKeyId(WifiEnterpriseConfig current)1304 public String getKeyId(WifiEnterpriseConfig current) { 1305 // If EAP method is not initialized, use current config details 1306 if (mEapMethod == Eap.NONE) { 1307 return (current != null) ? current.getKeyId(null) : EMPTY_VALUE; 1308 } 1309 if (!isEapMethodValid()) { 1310 return EMPTY_VALUE; 1311 } 1312 return Eap.strings[mEapMethod] + "_" + Phase2.strings[mPhase2Method]; 1313 } 1314 removeDoubleQuotes(String string)1315 private String removeDoubleQuotes(String string) { 1316 if (TextUtils.isEmpty(string)) return ""; 1317 int length = string.length(); 1318 if ((length > 1) && (string.charAt(0) == '"') 1319 && (string.charAt(length - 1) == '"')) { 1320 return string.substring(1, length - 1); 1321 } 1322 return string; 1323 } 1324 convertToQuotedString(String string)1325 private String convertToQuotedString(String string) { 1326 return "\"" + string + "\""; 1327 } 1328 1329 /** 1330 * Returns the index at which the toBeFound string is found in the array. 1331 * @param arr array of strings 1332 * @param toBeFound string to be found 1333 * @param defaultIndex default index to be returned when string is not found 1334 * @return the index into array 1335 */ getStringIndex(String arr[], String toBeFound, int defaultIndex)1336 private int getStringIndex(String arr[], String toBeFound, int defaultIndex) { 1337 if (TextUtils.isEmpty(toBeFound)) return defaultIndex; 1338 for (int i = 0; i < arr.length; i++) { 1339 if (toBeFound.equals(arr[i])) return i; 1340 } 1341 return defaultIndex; 1342 } 1343 1344 /** 1345 * Returns the field value for the key with prefix removed. 1346 * @param key into the hash 1347 * @param prefix is the prefix that the value may have 1348 * @return value 1349 * @hide 1350 */ getFieldValue(String key, String prefix)1351 private String getFieldValue(String key, String prefix) { 1352 // TODO: Should raise an exception if |key| is EAP_KEY or PHASE2_KEY since 1353 // neither of these keys should be retrieved in this manner. 1354 String value = mFields.get(key); 1355 // Uninitialized or known to be empty after reading from supplicant 1356 if (TextUtils.isEmpty(value) || EMPTY_VALUE.equals(value)) return ""; 1357 1358 value = removeDoubleQuotes(value); 1359 if (value.startsWith(prefix)) { 1360 return value.substring(prefix.length()); 1361 } else { 1362 return value; 1363 } 1364 } 1365 1366 /** 1367 * Returns the field value for the key. 1368 * @param key into the hash 1369 * @return value 1370 * @hide 1371 */ getFieldValue(String key)1372 public String getFieldValue(String key) { 1373 return getFieldValue(key, ""); 1374 } 1375 1376 /** 1377 * Set a value with an optional prefix at key 1378 * @param key into the hash 1379 * @param value to be set 1380 * @param prefix an optional value to be prefixed to actual value 1381 * @hide 1382 */ setFieldValue(String key, String value, String prefix)1383 private void setFieldValue(String key, String value, String prefix) { 1384 // TODO: Should raise an exception if |key| is EAP_KEY or PHASE2_KEY since 1385 // neither of these keys should be set in this manner. 1386 if (TextUtils.isEmpty(value)) { 1387 mFields.put(key, EMPTY_VALUE); 1388 } else { 1389 String valueToSet; 1390 if (!UNQUOTED_KEYS.contains(key)) { 1391 valueToSet = convertToQuotedString(prefix + value); 1392 } else { 1393 valueToSet = prefix + value; 1394 } 1395 mFields.put(key, valueToSet); 1396 } 1397 } 1398 1399 /** 1400 * Set a value at key 1401 * @param key into the hash 1402 * @param value to be set 1403 * @hide 1404 */ setFieldValue(String key, String value)1405 public void setFieldValue(String key, String value) { 1406 setFieldValue(key, value, ""); 1407 } 1408 1409 @Override toString()1410 public String toString() { 1411 StringBuffer sb = new StringBuffer(); 1412 for (String key : mFields.keySet()) { 1413 // Don't display password in toString(). 1414 String value = PASSWORD_KEY.equals(key) ? "<removed>" : mFields.get(key); 1415 sb.append(key).append(" ").append(value).append("\n"); 1416 } 1417 if (mEapMethod >= 0 && mEapMethod < Eap.strings.length) { 1418 sb.append("eap_method: ").append(Eap.strings[mEapMethod]).append("\n"); 1419 } 1420 if (mPhase2Method > 0 && mPhase2Method < Phase2.strings.length) { 1421 sb.append("phase2_method: ").append(Phase2.strings[mPhase2Method]).append("\n"); 1422 } 1423 sb.append(" ocsp: ").append(mOcsp).append("\n"); 1424 sb.append(" trust_on_first_use: ").append(mIsTrustOnFirstUseEnabled).append("\n"); 1425 sb.append(" user_approve_no_ca_cert: ").append(mUserApproveNoCaCert).append("\n"); 1426 sb.append(" selected_rcoi: ").append(mSelectedRcoi).append("\n"); 1427 return sb.toString(); 1428 } 1429 1430 /** 1431 * Returns whether the EAP method data is valid, i.e., whether mEapMethod and mPhase2Method 1432 * are valid indices into {@code Eap.strings[]} and {@code Phase2.strings[]} respectively. 1433 */ isEapMethodValid()1434 private boolean isEapMethodValid() { 1435 if (mEapMethod == Eap.NONE) { 1436 Log.e(TAG, "WiFi enterprise configuration is invalid as it supplies no EAP method."); 1437 return false; 1438 } 1439 if (mEapMethod < 0 || mEapMethod >= Eap.strings.length) { 1440 Log.e(TAG, "mEapMethod is invald for WiFi enterprise configuration: " + mEapMethod); 1441 return false; 1442 } 1443 if (mPhase2Method < 0 || mPhase2Method >= Phase2.strings.length) { 1444 Log.e(TAG, "mPhase2Method is invald for WiFi enterprise configuration: " 1445 + mPhase2Method); 1446 return false; 1447 } 1448 return true; 1449 } 1450 1451 /** 1452 * Check if certificate was installed by an app, or manually (not by an app). If true, 1453 * certificate and keys will be removed from key storage when this network is removed. If not, 1454 * then certificates and keys remain persistent until the user manually removes them. 1455 * 1456 * @return true if certificate was installed by an app, false if certificate was installed 1457 * manually by the user. 1458 * @hide 1459 */ isAppInstalledDeviceKeyAndCert()1460 public boolean isAppInstalledDeviceKeyAndCert() { 1461 return mIsAppInstalledDeviceKeyAndCert; 1462 } 1463 1464 /** 1465 * Initialize the value of the app installed device key and cert flag. 1466 * 1467 * @param isAppInstalledDeviceKeyAndCert true or false 1468 * @hide 1469 */ initIsAppInstalledDeviceKeyAndCert(boolean isAppInstalledDeviceKeyAndCert)1470 public void initIsAppInstalledDeviceKeyAndCert(boolean isAppInstalledDeviceKeyAndCert) { 1471 mIsAppInstalledDeviceKeyAndCert = isAppInstalledDeviceKeyAndCert; 1472 } 1473 1474 /** 1475 * Check if CA certificate was installed by an app, or manually (not by an app). If true, 1476 * CA certificate will be removed from key storage when this network is removed. If not, 1477 * then certificates and keys remain persistent until the user manually removes them. 1478 * 1479 * @return true if CA certificate was installed by an app, false if CA certificate was installed 1480 * manually by the user. 1481 * @hide 1482 */ isAppInstalledCaCert()1483 public boolean isAppInstalledCaCert() { 1484 return mIsAppInstalledCaCert; 1485 } 1486 1487 /** 1488 * Initialize the value of the app installed root CA cert flag. 1489 * 1490 * @param isAppInstalledCaCert true or false 1491 * @hide 1492 */ initIsAppInstalledCaCert(boolean isAppInstalledCaCert)1493 public void initIsAppInstalledCaCert(boolean isAppInstalledCaCert) { 1494 mIsAppInstalledCaCert = isAppInstalledCaCert; 1495 } 1496 1497 /** 1498 * Set the OCSP type. 1499 * @param ocsp is one of {@link ##OCSP_NONE}, {@link #OCSP_REQUEST_CERT_STATUS}, 1500 * {@link #OCSP_REQUIRE_CERT_STATUS} or 1501 * {@link #OCSP_REQUIRE_ALL_NON_TRUSTED_CERTS_STATUS} 1502 * @throws IllegalArgumentException if the OCSP type is invalid 1503 * @hide 1504 */ 1505 @SystemApi setOcsp(@csp int ocsp)1506 public void setOcsp(@Ocsp int ocsp) { 1507 if (ocsp >= OCSP_NONE && ocsp <= OCSP_REQUIRE_ALL_NON_TRUSTED_CERTS_STATUS) { 1508 mOcsp = ocsp; 1509 } else { 1510 throw new IllegalArgumentException("Invalid OCSP type."); 1511 } 1512 } 1513 1514 /** 1515 * Get the OCSP type. 1516 * @hide 1517 */ 1518 @SystemApi getOcsp()1519 public @Ocsp int getOcsp() { 1520 return mOcsp; 1521 } 1522 1523 /** 1524 * Utility method to determine whether the configuration's authentication method is SIM-based. 1525 * 1526 * @return true if the credential information requires SIM card for current authentication 1527 * method, otherwise it returns false. 1528 */ isAuthenticationSimBased()1529 public boolean isAuthenticationSimBased() { 1530 if (mEapMethod == Eap.SIM || mEapMethod == Eap.AKA || mEapMethod == Eap.AKA_PRIME) { 1531 return true; 1532 } 1533 if (mEapMethod == Eap.PEAP) { 1534 return mPhase2Method == Phase2.SIM || mPhase2Method == Phase2.AKA 1535 || mPhase2Method == Phase2.AKA_PRIME; 1536 } 1537 return false; 1538 } 1539 1540 /** 1541 * Set the WAPI certificate suite name on wpa_supplicant. 1542 * 1543 * If this field is not specified, WAPI-CERT uses ASU ID from WAI packet 1544 * as the certificate suite name automatically. 1545 * 1546 * @param wapiCertSuite The name for WAPI certificate suite, or empty string to clear. 1547 * @hide 1548 */ 1549 @SystemApi setWapiCertSuite(@onNull String wapiCertSuite)1550 public void setWapiCertSuite(@NonNull String wapiCertSuite) { 1551 setFieldValue(WAPI_CERT_SUITE_KEY, wapiCertSuite); 1552 } 1553 1554 /** 1555 * Get the WAPI certificate suite name 1556 * @return the certificate suite name 1557 * @hide 1558 */ 1559 @NonNull 1560 @SystemApi getWapiCertSuite()1561 public String getWapiCertSuite() { 1562 return getFieldValue(WAPI_CERT_SUITE_KEY); 1563 } 1564 1565 /** 1566 * Determines whether an Enterprise configuration's EAP method requires a Root CA certification 1567 * to validate the authentication server i.e. PEAP, TLS, UNAUTH_TLS, or TTLS. 1568 * @return True if configuration requires a CA certification, false otherwise. 1569 */ isEapMethodServerCertUsed()1570 public boolean isEapMethodServerCertUsed() { 1571 return mEapMethod == Eap.PEAP || mEapMethod == Eap.TLS || mEapMethod == Eap.TTLS 1572 || mEapMethod == Eap.UNAUTH_TLS; 1573 } 1574 /** 1575 * Determines whether an Enterprise configuration enables server certificate validation. 1576 * <p> 1577 * The caller can determine, along with {@link #isEapMethodServerCertUsed()}, if an 1578 * Enterprise configuration enables server certificate validation, which is a mandatory 1579 * requirement for networks that use TLS based EAP methods. A configuration that does not 1580 * enable server certificate validation will be ignored and will not be considered for 1581 * network selection. A network suggestion with such a configuration will cause an 1582 * IllegalArgumentException to be thrown when suggested. 1583 * Server validation is achieved by the following: 1584 * - Either certificate or CA path is configured. 1585 * - Either alternative subject match or domain suffix match is set. 1586 * @return True for server certificate validation is enabled, false otherwise. 1587 * @throws IllegalStateException on configuration which doesn't use server certificate. 1588 * @see #isEapMethodServerCertUsed() 1589 */ isServerCertValidationEnabled()1590 public boolean isServerCertValidationEnabled() { 1591 if (!isEapMethodServerCertUsed()) { 1592 throw new IllegalStateException("Configuration doesn't use server certificates for " 1593 + "authentication"); 1594 } 1595 return isMandatoryParameterSetForServerCertValidation(); 1596 } 1597 1598 /** 1599 * Helper method to check if mandatory parameter for server cert validation is set. 1600 * @hide 1601 */ isMandatoryParameterSetForServerCertValidation()1602 public boolean isMandatoryParameterSetForServerCertValidation() { 1603 if (TextUtils.isEmpty(getAltSubjectMatch()) 1604 && TextUtils.isEmpty(getDomainSuffixMatch())) { 1605 // Both subject and domain match are not set, validation is not enabled. 1606 return false; 1607 } 1608 if (mIsAppInstalledCaCert) { 1609 // CA certificate is installed by App, validation is enabled. 1610 return true; 1611 } 1612 if (getCaCertificateAliases() != null) { 1613 // CA certificate alias from keyStore is set, validation is enabled. 1614 return true; 1615 } 1616 return !TextUtils.isEmpty(getCaPath()); 1617 } 1618 1619 /** 1620 * Check if a given certificate Get the Suite-B cipher from the certificate 1621 * 1622 * @param x509Certificate Certificate to process 1623 * @return true if the certificate OID matches the Suite-B requirements for RSA or ECDSA 1624 * certificates, or false otherwise. 1625 * @hide 1626 */ isSuiteBCipherCert(@ullable X509Certificate x509Certificate)1627 public static boolean isSuiteBCipherCert(@Nullable X509Certificate x509Certificate) { 1628 if (x509Certificate == null) { 1629 return false; 1630 } 1631 final String sigAlgOid = x509Certificate.getSigAlgOID(); 1632 1633 // Wi-Fi alliance requires the use of both ECDSA secp384r1 and RSA 3072 certificates 1634 // in WPA3-Enterprise 192-bit security networks, which are also known as Suite-B-192 1635 // networks, even though NSA Suite-B-192 mandates ECDSA only. The use of the term 1636 // Suite-B was already coined in the IEEE 802.11-2016 specification for 1637 // AKM 00-0F-AC but the test plan for WPA3-Enterprise 192-bit for APs mandates 1638 // support for both RSA and ECDSA, and for STAs it mandates ECDSA and optionally 1639 // RSA. In order to be compatible with all WPA3-Enterprise 192-bit deployments, 1640 // we are supporting both types here. 1641 if (sigAlgOid.equals("1.2.840.113549.1.1.12")) { 1642 // sha384WithRSAEncryption 1643 if (x509Certificate.getPublicKey() instanceof RSAPublicKey) { 1644 final RSAPublicKey rsaPublicKey = (RSAPublicKey) x509Certificate.getPublicKey(); 1645 if (rsaPublicKey.getModulus() != null 1646 && rsaPublicKey.getModulus().bitLength() >= 3072) { 1647 return true; 1648 } 1649 } 1650 } else if (sigAlgOid.equals("1.2.840.10045.4.3.3")) { 1651 // ecdsa-with-SHA384 1652 if (x509Certificate.getPublicKey() instanceof ECPublicKey) { 1653 final ECPublicKey ecPublicKey = (ECPublicKey) x509Certificate.getPublicKey(); 1654 final ECParameterSpec ecParameterSpec = ecPublicKey.getParams(); 1655 1656 if (ecParameterSpec != null && ecParameterSpec.getOrder() != null 1657 && ecParameterSpec.getOrder().bitLength() >= 384) { 1658 return true; 1659 } 1660 } 1661 } 1662 return false; 1663 } 1664 1665 /** 1666 * Set a prefix for a decorated identity as per RFC 7542. 1667 * This prefix must contain a list of realms (could be a list of 1) delimited by a '!' 1668 * character. e.g. homerealm.example.org! or proxyrealm.example.net!homerealm.example.org! 1669 * A prefix of "homerealm.example.org!" will generate a decorated identity that 1670 * looks like: homerealm.example.org!user@otherrealm.example.net 1671 * Calling with a null parameter will clear the decorated prefix. 1672 * Note: Caller must verify that the device supports this feature by calling 1673 * {@link WifiManager#isDecoratedIdentitySupported()} 1674 * 1675 * @param decoratedIdentityPrefix The prefix to add to the outer/anonymous identity 1676 */ 1677 @RequiresApi(Build.VERSION_CODES.S) setDecoratedIdentityPrefix(@ullable String decoratedIdentityPrefix)1678 public void setDecoratedIdentityPrefix(@Nullable String decoratedIdentityPrefix) { 1679 if (!SdkLevel.isAtLeastS()) { 1680 throw new UnsupportedOperationException(); 1681 } 1682 if (!TextUtils.isEmpty(decoratedIdentityPrefix) && !decoratedIdentityPrefix.endsWith("!")) { 1683 throw new IllegalArgumentException( 1684 "Decorated identity prefix must be delimited by '!'"); 1685 } 1686 setFieldValue(DECORATED_IDENTITY_PREFIX_KEY, decoratedIdentityPrefix); 1687 } 1688 1689 /** 1690 * Get the decorated identity prefix. 1691 * 1692 * @return The decorated identity prefix 1693 */ 1694 @RequiresApi(Build.VERSION_CODES.S) getDecoratedIdentityPrefix()1695 public @Nullable String getDecoratedIdentityPrefix() { 1696 if (!SdkLevel.isAtLeastS()) { 1697 throw new UnsupportedOperationException(); 1698 } 1699 final String decoratedId = getFieldValue(DECORATED_IDENTITY_PREFIX_KEY); 1700 return decoratedId.isEmpty() ? null : decoratedId; 1701 } 1702 1703 /** 1704 * Enable Trust On First Use. 1705 * 1706 * Trust On First Use (TOFU) simplifies manual or partial configurations 1707 * of TLS-based EAP networks. TOFU operates by installing the Root CA cert 1708 * which is received from the server during an initial connection to a new network. 1709 * Such installation is gated by user approval. 1710 * Use only when it is not possible to configure the Root CA cert for the server. 1711 * <br> 1712 * Note: If a Root CA cert is already configured, this option is ignored, 1713 * e.g. if {@link #setCaCertificate(X509Certificate)}, or 1714 * {@link #setCaCertificates(X509Certificate[])} is called. 1715 * 1716 * @param enable true to enable; false otherwise (the default if the method is not called). 1717 */ enableTrustOnFirstUse(boolean enable)1718 public void enableTrustOnFirstUse(boolean enable) { 1719 mIsTrustOnFirstUseEnabled = enable; 1720 } 1721 1722 /** 1723 * Indicates whether or not Trust On First Use (TOFU) is enabled. 1724 * 1725 * @return Trust On First Use is enabled or not. 1726 */ isTrustOnFirstUseEnabled()1727 public boolean isTrustOnFirstUseEnabled() { 1728 return mIsTrustOnFirstUseEnabled; 1729 } 1730 1731 /** 1732 * For devices with no TOFU support, indicate that the user approved that a 1733 * legacy TLS-based EAP configuration from a previous release can be used 1734 * without a Root CA certificate. 1735 * 1736 * @hide 1737 */ setUserApproveNoCaCert(boolean approved)1738 public void setUserApproveNoCaCert(boolean approved) { 1739 mUserApproveNoCaCert = approved; 1740 } 1741 1742 /** 1743 * For devices with no TOFU support, indicates if the user approved that a 1744 * legacy TLS-based EAP configuration from a previous release can be used 1745 * without a Root CA certificate. 1746 * 1747 * @return indicate whether a user approves this no CA cert config. 1748 * @hide 1749 */ isUserApproveNoCaCert()1750 public boolean isUserApproveNoCaCert() { 1751 return mUserApproveNoCaCert; 1752 } 1753 1754 } 1755