WifiEnterpriseConfig.java revision 825fb3492a02b4a4101bf9039d2f020ad131193f
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 */ 16package android.net.wifi; 17 18import android.annotation.Nullable; 19import android.os.Parcel; 20import android.os.Parcelable; 21import android.security.Credentials; 22import android.text.TextUtils; 23import android.util.Log; 24 25import java.io.ByteArrayInputStream; 26import java.nio.charset.StandardCharsets; 27import java.security.KeyFactory; 28import java.security.NoSuchAlgorithmException; 29import java.security.PrivateKey; 30import java.security.cert.CertificateEncodingException; 31import java.security.cert.CertificateException; 32import java.security.cert.CertificateFactory; 33import java.security.cert.X509Certificate; 34import java.security.spec.InvalidKeySpecException; 35import java.security.spec.PKCS8EncodedKeySpec; 36import java.util.HashMap; 37import java.util.Map; 38 39/** 40 * Enterprise configuration details for Wi-Fi. Stores details about the EAP method 41 * and any associated credentials. 42 */ 43public 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 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 */ 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 163 public int describeContents() { 164 return 0; 165 } 166 167 @Override 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 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 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 */ 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 */ 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 */ 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 */ 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 */ 373 public boolean saveToSupplicant(SupplicantSaver saver) { 374 if (!isEapMethodValid()) { 375 return false; 376 } 377 378 for (String key : mFields.keySet()) { 379 if (!saver.saveValue(key, mFields.get(key))) { 380 return false; 381 } 382 } 383 384 if (!saver.saveValue(EAP_KEY, Eap.strings[mEapMethod])) { 385 return false; 386 } 387 388 if (mEapMethod != Eap.TLS && mPhase2Method != Phase2.NONE) { 389 boolean is_autheap = mEapMethod == Eap.TTLS && mPhase2Method == Phase2.GTC; 390 String prefix = is_autheap ? Phase2.AUTHEAP_PREFIX : Phase2.AUTH_PREFIX; 391 return saver.saveValue(PHASE2_KEY, prefix + Phase2.strings[mPhase2Method]); 392 } else if (mPhase2Method == Phase2.NONE) { 393 // By default, send a null phase 2 to clear old configuration values. 394 return saver.saveValue(PHASE2_KEY, null); 395 } else { 396 Log.e(TAG, "WiFi enterprise configuration is invalid as it supplies a " 397 + "phase 2 method but the phase1 method does not support it."); 398 return false; 399 } 400 } 401 402 /** 403 * Internal use only; retrieve configuration from wpa_supplicant config. 404 * @param loader proxy for retrieving configuration keys from wpa_supplicant 405 * @hide 406 */ 407 public void loadFromSupplicant(SupplicantLoader loader) { 408 for (String key : SUPPLICANT_CONFIG_KEYS) { 409 String value = loader.loadValue(key); 410 if (value == null) { 411 mFields.put(key, EMPTY_VALUE); 412 } else { 413 mFields.put(key, value); 414 } 415 } 416 String eapMethod = loader.loadValue(EAP_KEY); 417 mEapMethod = getStringIndex(Eap.strings, eapMethod, Eap.NONE); 418 419 String phase2Method = removeDoubleQuotes(loader.loadValue(PHASE2_KEY)); 420 // Remove "auth=" or "autheap=" prefix. 421 if (phase2Method.startsWith(Phase2.AUTH_PREFIX)) { 422 phase2Method = phase2Method.substring(Phase2.AUTH_PREFIX.length()); 423 } else if (phase2Method.startsWith(Phase2.AUTHEAP_PREFIX)) { 424 phase2Method = phase2Method.substring(Phase2.AUTHEAP_PREFIX.length()); 425 } 426 mPhase2Method = getStringIndex(Phase2.strings, phase2Method, Phase2.NONE); 427 } 428 429 /** 430 * Set the EAP authentication method. 431 * @param eapMethod is one {@link Eap#PEAP}, {@link Eap#TLS}, {@link Eap#TTLS} or 432 * {@link Eap#PWD} 433 * @throws IllegalArgumentException on an invalid eap method 434 */ 435 public void setEapMethod(int eapMethod) { 436 switch (eapMethod) { 437 /** Valid methods */ 438 case Eap.TLS: 439 setPhase2Method(Phase2.NONE); 440 /* fall through */ 441 case Eap.PEAP: 442 case Eap.PWD: 443 case Eap.TTLS: 444 case Eap.SIM: 445 case Eap.AKA: 446 case Eap.AKA_PRIME: 447 mEapMethod = eapMethod; 448 mFields.put(OPP_KEY_CACHING, "1"); 449 break; 450 default: 451 throw new IllegalArgumentException("Unknown EAP method"); 452 } 453 } 454 455 /** 456 * Get the eap method. 457 * @return eap method configured 458 */ 459 public int getEapMethod() { 460 return mEapMethod; 461 } 462 463 /** 464 * Set Phase 2 authentication method. Sets the inner authentication method to be used in 465 * phase 2 after setting up a secure channel 466 * @param phase2Method is the inner authentication method and can be one of {@link Phase2#NONE}, 467 * {@link Phase2#PAP}, {@link Phase2#MSCHAP}, {@link Phase2#MSCHAPV2}, 468 * {@link Phase2#GTC} 469 * @throws IllegalArgumentException on an invalid phase2 method 470 * 471 */ 472 public void setPhase2Method(int phase2Method) { 473 switch (phase2Method) { 474 case Phase2.NONE: 475 case Phase2.PAP: 476 case Phase2.MSCHAP: 477 case Phase2.MSCHAPV2: 478 case Phase2.GTC: 479 mPhase2Method = phase2Method; 480 break; 481 default: 482 throw new IllegalArgumentException("Unknown Phase 2 method"); 483 } 484 } 485 486 /** 487 * Get the phase 2 authentication method. 488 * @return a phase 2 method defined at {@link Phase2} 489 * */ 490 public int getPhase2Method() { 491 return mPhase2Method; 492 } 493 494 /** 495 * Set the identity 496 * @param identity 497 */ 498 public void setIdentity(String identity) { 499 setFieldValue(IDENTITY_KEY, identity, ""); 500 } 501 502 /** 503 * Get the identity 504 * @return the identity 505 */ 506 public String getIdentity() { 507 return getFieldValue(IDENTITY_KEY, ""); 508 } 509 510 /** 511 * Set anonymous identity. This is used as the unencrypted identity with 512 * certain EAP types 513 * @param anonymousIdentity the anonymous identity 514 */ 515 public void setAnonymousIdentity(String anonymousIdentity) { 516 setFieldValue(ANON_IDENTITY_KEY, anonymousIdentity, ""); 517 } 518 519 /** 520 * Get the anonymous identity 521 * @return anonymous identity 522 */ 523 public String getAnonymousIdentity() { 524 return getFieldValue(ANON_IDENTITY_KEY, ""); 525 } 526 527 /** 528 * Set the password. 529 * @param password the password 530 */ 531 public void setPassword(String password) { 532 setFieldValue(PASSWORD_KEY, password, ""); 533 } 534 535 /** 536 * Get the password. 537 * 538 * Returns locally set password value. For networks fetched from 539 * framework, returns "*". 540 */ 541 public String getPassword() { 542 return getFieldValue(PASSWORD_KEY, ""); 543 } 544 545 /** 546 * Encode a CA certificate alias so it does not contain illegal character. 547 * @hide 548 */ 549 public static String encodeCaCertificateAlias(String alias) { 550 byte[] bytes = alias.getBytes(StandardCharsets.UTF_8); 551 StringBuilder sb = new StringBuilder(bytes.length * 2); 552 for (byte o : bytes) { 553 sb.append(String.format("%02x", o & 0xFF)); 554 } 555 return sb.toString(); 556 } 557 558 /** 559 * Decode a previously-encoded CA certificate alias. 560 * @hide 561 */ 562 public static String decodeCaCertificateAlias(String alias) { 563 byte[] data = new byte[alias.length() >> 1]; 564 for (int n = 0, position = 0; n < alias.length(); n += 2, position++) { 565 data[position] = (byte) Integer.parseInt(alias.substring(n, n + 2), 16); 566 } 567 try { 568 return new String(data, StandardCharsets.UTF_8); 569 } catch (NumberFormatException e) { 570 e.printStackTrace(); 571 return alias; 572 } 573 } 574 575 /** 576 * Set CA certificate alias. 577 * 578 * <p> See the {@link android.security.KeyChain} for details on installing or choosing 579 * a certificate 580 * </p> 581 * @param alias identifies the certificate 582 * @hide 583 */ 584 public void setCaCertificateAlias(String alias) { 585 setFieldValue(CA_CERT_KEY, alias, CA_CERT_PREFIX); 586 } 587 588 /** 589 * Set CA certificate aliases. When creating installing the corresponding certificate to 590 * the keystore, please use alias encoded by {@link #encodeCaCertificateAlias(String)}. 591 * 592 * <p> See the {@link android.security.KeyChain} for details on installing or choosing 593 * a certificate. 594 * </p> 595 * @param aliases identifies the certificate 596 * @hide 597 */ 598 public void setCaCertificateAliases(@Nullable String[] aliases) { 599 if (aliases == null) { 600 setFieldValue(CA_CERT_KEY, null, CA_CERT_PREFIX); 601 } else if (aliases.length == 1) { 602 // Backwards compatibility: use the original cert prefix if setting only one alias. 603 setCaCertificateAlias(aliases[0]); 604 } else { 605 // Use KEYSTORES_URI which supports multiple aliases. 606 StringBuilder sb = new StringBuilder(); 607 for (int i = 0; i < aliases.length; i++) { 608 if (i > 0) { 609 sb.append(CA_CERT_ALIAS_DELIMITER); 610 } 611 sb.append(encodeCaCertificateAlias(Credentials.CA_CERTIFICATE + aliases[i])); 612 } 613 setFieldValue(CA_CERT_KEY, sb.toString(), KEYSTORES_URI); 614 } 615 } 616 617 /** 618 * Get CA certificate alias 619 * @return alias to the CA certificate 620 * @hide 621 */ 622 public String getCaCertificateAlias() { 623 return getFieldValue(CA_CERT_KEY, CA_CERT_PREFIX); 624 } 625 626 /** 627 * Get CA certificate aliases 628 * @return alias to the CA certificate 629 * @hide 630 */ 631 @Nullable public String[] getCaCertificateAliases() { 632 String value = getFieldValue(CA_CERT_KEY, ""); 633 if (value.startsWith(CA_CERT_PREFIX)) { 634 // Backwards compatibility: parse the original alias prefix. 635 return new String[] {getFieldValue(CA_CERT_KEY, CA_CERT_PREFIX)}; 636 } else if (value.startsWith(KEYSTORES_URI)) { 637 String values = value.substring(KEYSTORES_URI.length()); 638 639 String[] aliases = TextUtils.split(values, CA_CERT_ALIAS_DELIMITER); 640 for (int i = 0; i < aliases.length; i++) { 641 aliases[i] = decodeCaCertificateAlias(aliases[i]); 642 if (aliases[i].startsWith(Credentials.CA_CERTIFICATE)) { 643 aliases[i] = aliases[i].substring(Credentials.CA_CERTIFICATE.length()); 644 } 645 } 646 return aliases.length != 0 ? aliases : null; 647 } else { 648 return TextUtils.isEmpty(value) ? null : new String[] {value}; 649 } 650 } 651 652 /** 653 * Specify a X.509 certificate that identifies the server. 654 * 655 * <p>A default name is automatically assigned to the certificate and used 656 * with this configuration. The framework takes care of installing the 657 * certificate when the config is saved and removing the certificate when 658 * the config is removed. 659 * 660 * @param cert X.509 CA certificate 661 * @throws IllegalArgumentException if not a CA certificate 662 */ 663 public void setCaCertificate(@Nullable X509Certificate cert) { 664 if (cert != null) { 665 if (cert.getBasicConstraints() >= 0) { 666 mCaCerts = new X509Certificate[] {cert}; 667 } else { 668 throw new IllegalArgumentException("Not a CA certificate"); 669 } 670 } else { 671 mCaCerts = null; 672 } 673 } 674 675 /** 676 * Get CA certificate. If multiple CA certificates are configured previously, 677 * return the first one. 678 * @return X.509 CA certificate 679 */ 680 @Nullable public X509Certificate getCaCertificate() { 681 if (mCaCerts != null && mCaCerts.length > 0) { 682 return mCaCerts[0]; 683 } else { 684 return null; 685 } 686 } 687 688 /** 689 * Specify a list of X.509 certificates that identifies the server. The validation 690 * passes if the CA of server certificate matches one of the given certificates. 691 692 * <p>Default names are automatically assigned to the certificates and used 693 * with this configuration. The framework takes care of installing the 694 * certificates when the config is saved and removing the certificates when 695 * the config is removed. 696 * 697 * @param certs X.509 CA certificates 698 * @throws IllegalArgumentException if any of the provided certificates is 699 * not a CA certificate 700 */ 701 public void setCaCertificates(@Nullable X509Certificate[] certs) { 702 if (certs != null) { 703 X509Certificate[] newCerts = new X509Certificate[certs.length]; 704 for (int i = 0; i < certs.length; i++) { 705 if (certs[i].getBasicConstraints() >= 0) { 706 newCerts[i] = certs[i]; 707 } else { 708 throw new IllegalArgumentException("Not a CA certificate"); 709 } 710 } 711 mCaCerts = newCerts; 712 } else { 713 mCaCerts = null; 714 } 715 } 716 717 /** 718 * Get CA certificates. 719 */ 720 @Nullable public X509Certificate[] getCaCertificates() { 721 if (mCaCerts != null || mCaCerts.length > 0) { 722 return mCaCerts; 723 } else { 724 return null; 725 } 726 } 727 728 /** 729 * @hide 730 */ 731 public void resetCaCertificate() { 732 mCaCerts = null; 733 } 734 735 /** 736 * Set the ca_path directive on wpa_supplicant. 737 * 738 * From wpa_supplicant documentation: 739 * 740 * Directory path for CA certificate files (PEM). This path may contain 741 * multiple CA certificates in OpenSSL format. Common use for this is to 742 * point to system trusted CA list which is often installed into directory 743 * like /etc/ssl/certs. If configured, these certificates are added to the 744 * list of trusted CAs. ca_cert may also be included in that case, but it is 745 * not required. 746 * @param domain The path for CA certificate files 747 * @hide 748 */ 749 public void setCaPath(String path) { 750 setFieldValue(CA_PATH_KEY, path); 751 } 752 753 /** 754 * Get the domain_suffix_match value. See setDomSuffixMatch. 755 * @return The path for CA certificate files. 756 * @hide 757 */ 758 public String getCaPath() { 759 return getFieldValue(CA_PATH_KEY, ""); 760 } 761 762 /** Set Client certificate alias. 763 * 764 * <p> See the {@link android.security.KeyChain} for details on installing or choosing 765 * a certificate 766 * </p> 767 * @param alias identifies the certificate 768 * @hide 769 */ 770 public void setClientCertificateAlias(String alias) { 771 setFieldValue(CLIENT_CERT_KEY, alias, CLIENT_CERT_PREFIX); 772 setFieldValue(PRIVATE_KEY_ID_KEY, alias, Credentials.USER_PRIVATE_KEY); 773 // Also, set engine parameters 774 if (TextUtils.isEmpty(alias)) { 775 mFields.put(ENGINE_KEY, ENGINE_DISABLE); 776 mFields.put(ENGINE_ID_KEY, EMPTY_VALUE); 777 } else { 778 mFields.put(ENGINE_KEY, ENGINE_ENABLE); 779 mFields.put(ENGINE_ID_KEY, convertToQuotedString(ENGINE_ID_KEYSTORE)); 780 } 781 } 782 783 /** 784 * Get client certificate alias 785 * @return alias to the client certificate 786 * @hide 787 */ 788 public String getClientCertificateAlias() { 789 return getFieldValue(CLIENT_CERT_KEY, CLIENT_CERT_PREFIX); 790 } 791 792 /** 793 * Specify a private key and client certificate for client authorization. 794 * 795 * <p>A default name is automatically assigned to the key entry and used 796 * with this configuration. The framework takes care of installing the 797 * key entry when the config is saved and removing the key entry when 798 * the config is removed. 799 800 * @param privateKey 801 * @param clientCertificate 802 * @throws IllegalArgumentException for an invalid key or certificate. 803 */ 804 public void setClientKeyEntry(PrivateKey privateKey, X509Certificate clientCertificate) { 805 if (clientCertificate != null) { 806 if (clientCertificate.getBasicConstraints() != -1) { 807 throw new IllegalArgumentException("Cannot be a CA certificate"); 808 } 809 if (privateKey == null) { 810 throw new IllegalArgumentException("Client cert without a private key"); 811 } 812 if (privateKey.getEncoded() == null) { 813 throw new IllegalArgumentException("Private key cannot be encoded"); 814 } 815 } 816 817 mClientPrivateKey = privateKey; 818 mClientCertificate = clientCertificate; 819 } 820 821 /** 822 * Get client certificate 823 * 824 * @return X.509 client certificate 825 */ 826 public X509Certificate getClientCertificate() { 827 return mClientCertificate; 828 } 829 830 /** 831 * @hide 832 */ 833 public void resetClientKeyEntry() { 834 mClientPrivateKey = null; 835 mClientCertificate = null; 836 } 837 838 /** 839 * @hide 840 */ 841 public PrivateKey getClientPrivateKey() { 842 return mClientPrivateKey; 843 } 844 845 /** 846 * Set subject match (deprecated). This is the substring to be matched against the subject of 847 * the authentication server certificate. 848 * @param subjectMatch substring to be matched 849 * @deprecated in favor of altSubjectMatch 850 */ 851 public void setSubjectMatch(String subjectMatch) { 852 setFieldValue(SUBJECT_MATCH_KEY, subjectMatch, ""); 853 } 854 855 /** 856 * Get subject match (deprecated) 857 * @return the subject match string 858 * @deprecated in favor of altSubjectMatch 859 */ 860 public String getSubjectMatch() { 861 return getFieldValue(SUBJECT_MATCH_KEY, ""); 862 } 863 864 /** 865 * Set alternate subject match. This is the substring to be matched against the 866 * alternate subject of the authentication server certificate. 867 * @param altSubjectMatch substring to be matched, for example 868 * DNS:server.example.com;EMAIL:server@example.com 869 */ 870 public void setAltSubjectMatch(String altSubjectMatch) { 871 setFieldValue(ALTSUBJECT_MATCH_KEY, altSubjectMatch, ""); 872 } 873 874 /** 875 * Get alternate subject match 876 * @return the alternate subject match string 877 */ 878 public String getAltSubjectMatch() { 879 return getFieldValue(ALTSUBJECT_MATCH_KEY, ""); 880 } 881 882 /** 883 * Set the domain_suffix_match directive on wpa_supplicant. This is the parameter to use 884 * for Hotspot 2.0 defined matching of AAA server certs per WFA HS2.0 spec, section 7.3.3.2, 885 * second paragraph. 886 * 887 * From wpa_supplicant documentation: 888 * Constraint for server domain name. If set, this FQDN is used as a suffix match requirement 889 * for the AAAserver certificate in SubjectAltName dNSName element(s). If a matching dNSName is 890 * found, this constraint is met. If no dNSName values are present, this constraint is matched 891 * against SubjectName CN using same suffix match comparison. 892 * Suffix match here means that the host/domain name is compared one label at a time starting 893 * from the top-level domain and all the labels in domain_suffix_match shall be included in the 894 * certificate. The certificate may include additional sub-level labels in addition to the 895 * required labels. 896 * For example, domain_suffix_match=example.com would match test.example.com but would not 897 * match test-example.com. 898 * @param domain The domain value 899 */ 900 public void setDomainSuffixMatch(String domain) { 901 setFieldValue(DOM_SUFFIX_MATCH_KEY, domain); 902 } 903 904 /** 905 * Get the domain_suffix_match value. See setDomSuffixMatch. 906 * @return The domain value. 907 */ 908 public String getDomainSuffixMatch() { 909 return getFieldValue(DOM_SUFFIX_MATCH_KEY, ""); 910 } 911 912 /** 913 * Set realm for passpoint credential; realm identifies a set of networks where your 914 * passpoint credential can be used 915 * @param realm the realm 916 */ 917 public void setRealm(String realm) { 918 setFieldValue(REALM_KEY, realm, ""); 919 } 920 921 /** 922 * Get realm for passpoint credential; see {@link #setRealm(String)} for more information 923 * @return the realm 924 */ 925 public String getRealm() { 926 return getFieldValue(REALM_KEY, ""); 927 } 928 929 /** 930 * Set plmn (Public Land Mobile Network) of the provider of passpoint credential 931 * @param plmn the plmn value derived from mcc (mobile country code) & mnc (mobile network code) 932 */ 933 public void setPlmn(String plmn) { 934 setFieldValue(PLMN_KEY, plmn, ""); 935 } 936 937 /** 938 * Get plmn (Public Land Mobile Network) for passpoint credential; see {@link #setPlmn 939 * (String)} for more information 940 * @return the plmn 941 */ 942 public String getPlmn() { 943 return getFieldValue(PLMN_KEY, ""); 944 } 945 946 /** See {@link WifiConfiguration#getKeyIdForCredentials} @hide */ 947 public String getKeyId(WifiEnterpriseConfig current) { 948 // If EAP method is not initialized, use current config details 949 if (mEapMethod == Eap.NONE) { 950 return (current != null) ? current.getKeyId(null) : EMPTY_VALUE; 951 } 952 if (!isEapMethodValid()) { 953 return EMPTY_VALUE; 954 } 955 return Eap.strings[mEapMethod] + "_" + Phase2.strings[mPhase2Method]; 956 } 957 958 private String removeDoubleQuotes(String string) { 959 if (TextUtils.isEmpty(string)) return ""; 960 int length = string.length(); 961 if ((length > 1) && (string.charAt(0) == '"') 962 && (string.charAt(length - 1) == '"')) { 963 return string.substring(1, length - 1); 964 } 965 return string; 966 } 967 968 private String convertToQuotedString(String string) { 969 return "\"" + string + "\""; 970 } 971 972 /** 973 * Returns the index at which the toBeFound string is found in the array. 974 * @param arr array of strings 975 * @param toBeFound string to be found 976 * @param defaultIndex default index to be returned when string is not found 977 * @return the index into array 978 */ 979 private int getStringIndex(String arr[], String toBeFound, int defaultIndex) { 980 if (TextUtils.isEmpty(toBeFound)) return defaultIndex; 981 for (int i = 0; i < arr.length; i++) { 982 if (toBeFound.equals(arr[i])) return i; 983 } 984 return defaultIndex; 985 } 986 987 /** 988 * Returns the field value for the key. 989 * @param key into the hash 990 * @param prefix is the prefix that the value may have 991 * @return value 992 * @hide 993 */ 994 public String getFieldValue(String key, String prefix) { 995 // TODO: Should raise an exception if |key| is EAP_KEY or PHASE2_KEY since 996 // neither of these keys should be retrieved in this manner. 997 String value = mFields.get(key); 998 // Uninitialized or known to be empty after reading from supplicant 999 if (TextUtils.isEmpty(value) || EMPTY_VALUE.equals(value)) return ""; 1000 1001 value = removeDoubleQuotes(value); 1002 if (value.startsWith(prefix)) { 1003 return value.substring(prefix.length()); 1004 } else { 1005 return value; 1006 } 1007 } 1008 1009 /** 1010 * Set a value with an optional prefix at key 1011 * @param key into the hash 1012 * @param value to be set 1013 * @param prefix an optional value to be prefixed to actual value 1014 * @hide 1015 */ 1016 public void setFieldValue(String key, String value, String prefix) { 1017 // TODO: Should raise an exception if |key| is EAP_KEY or PHASE2_KEY since 1018 // neither of these keys should be set in this manner. 1019 if (TextUtils.isEmpty(value)) { 1020 mFields.put(key, EMPTY_VALUE); 1021 } else { 1022 mFields.put(key, convertToQuotedString(prefix + value)); 1023 } 1024 } 1025 1026 1027 /** 1028 * Set a value with an optional prefix at key 1029 * @param key into the hash 1030 * @param value to be set 1031 * @param prefix an optional value to be prefixed to actual value 1032 * @hide 1033 */ 1034 public void setFieldValue(String key, String value) { 1035 // TODO: Should raise an exception if |key| is EAP_KEY or PHASE2_KEY since 1036 // neither of these keys should be set in this manner. 1037 if (TextUtils.isEmpty(value)) { 1038 mFields.put(key, EMPTY_VALUE); 1039 } else { 1040 mFields.put(key, convertToQuotedString(value)); 1041 } 1042 } 1043 1044 @Override 1045 public String toString() { 1046 StringBuffer sb = new StringBuffer(); 1047 for (String key : mFields.keySet()) { 1048 // Don't display password in toString(). 1049 String value = (key == PASSWORD_KEY) ? "<removed>" : mFields.get(key); 1050 sb.append(key).append(" ").append(value).append("\n"); 1051 } 1052 return sb.toString(); 1053 } 1054 1055 /** 1056 * Returns whether the EAP method data is valid, i.e., whether mEapMethod and mPhase2Method 1057 * are valid indices into {@code Eap.strings[]} and {@code Phase2.strings[]} respectively. 1058 */ 1059 private boolean isEapMethodValid() { 1060 if (mEapMethod == Eap.NONE) { 1061 Log.e(TAG, "WiFi enterprise configuration is invalid as it supplies no EAP method."); 1062 return false; 1063 } 1064 if (mEapMethod < 0 || mEapMethod >= Eap.strings.length) { 1065 Log.e(TAG, "mEapMethod is invald for WiFi enterprise configuration: " + mEapMethod); 1066 return false; 1067 } 1068 if (mPhase2Method < 0 || mPhase2Method >= Phase2.strings.length) { 1069 Log.e(TAG, "mPhase2Method is invald for WiFi enterprise configuration: " 1070 + mPhase2Method); 1071 return false; 1072 } 1073 return true; 1074 } 1075} 1076