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