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