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