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