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