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