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