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