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