WifiEnterpriseConfig.java revision 8d06e430307d07e3d51f381b4c7c89f7d9155133
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.os.Parcel; 19import android.os.Parcelable; 20import android.os.Process; 21import android.security.Credentials; 22import android.security.KeyStore; 23import android.text.TextUtils; 24 25import java.io.ByteArrayInputStream; 26import java.io.IOException; 27import java.security.KeyFactory; 28import java.security.NoSuchAlgorithmException; 29import java.security.PrivateKey; 30import java.security.cert.Certificate; 31import java.security.cert.CertificateEncodingException; 32import java.security.cert.CertificateException; 33import java.security.cert.CertificateFactory; 34import java.security.cert.X509Certificate; 35import java.security.spec.InvalidKeySpecException; 36import java.security.spec.PKCS8EncodedKeySpec; 37import java.util.HashMap; 38import java.util.Map; 39 40/** 41 * Enterprise configuration details for Wi-Fi. Stores details about the EAP method 42 * and any associated credentials. 43 */ 44public class WifiEnterpriseConfig implements Parcelable { 45 private static final String TAG = "WifiEnterpriseConfig"; 46 /** 47 * In old configurations, the "private_key" field was used. However, newer 48 * configurations use the key_id field with the engine_id set to "keystore". 49 * If this field is found in the configuration, the migration code is 50 * triggered. 51 */ 52 private static final String OLD_PRIVATE_KEY_NAME = "private_key"; 53 54 /** 55 * String representing the keystore OpenSSL ENGINE's ID. 56 */ 57 private static final String ENGINE_ID_KEYSTORE = "keystore"; 58 59 /** 60 * String representing the keystore URI used for wpa_supplicant. 61 */ 62 private static final String KEYSTORE_URI = "keystore://"; 63 64 /** 65 * String to set the engine value to when it should be enabled. 66 */ 67 private static final String ENGINE_ENABLE = "1"; 68 69 /** 70 * String to set the engine value to when it should be disabled. 71 */ 72 private static final String ENGINE_DISABLE = "0"; 73 74 private static final String CA_CERT_PREFIX = KEYSTORE_URI + Credentials.CA_CERTIFICATE; 75 private static final String CLIENT_CERT_PREFIX = KEYSTORE_URI + Credentials.USER_CERTIFICATE; 76 77 private static final String EAP_KEY = "eap"; 78 private static final String PHASE2_KEY = "phase2"; 79 private static final String IDENTITY_KEY = "identity"; 80 private static final String ANON_IDENTITY_KEY = "anonymous_identity"; 81 private static final String PASSWORD_KEY = "password"; 82 private static final String CLIENT_CERT_KEY = "client_cert"; 83 private static final String CA_CERT_KEY = "ca_cert"; 84 private static final String SUBJECT_MATCH_KEY = "subject_match"; 85 private static final String ENGINE_KEY = "engine"; 86 private static final String ENGINE_ID_KEY = "engine_id"; 87 private static final String PRIVATE_KEY_ID_KEY = "key_id"; 88 private static final String OPP_KEY_CACHING = "proactive_key_caching"; 89 90 private HashMap<String, String> mFields = new HashMap<String, String>(); 91 private X509Certificate mCaCert; 92 private PrivateKey mClientPrivateKey; 93 private X509Certificate mClientCertificate; 94 95 /** This represents an empty value of an enterprise field. 96 * NULL is used at wpa_supplicant to indicate an empty value 97 */ 98 static final String EMPTY_VALUE = "NULL"; 99 100 public WifiEnterpriseConfig() { 101 // Do not set defaults so that the enterprise fields that are not changed 102 // by API are not changed underneath 103 // This is essential because an app may not have all fields like password 104 // available. It allows modification of subset of fields. 105 106 } 107 108 /** Copy constructor */ 109 public WifiEnterpriseConfig(WifiEnterpriseConfig source) { 110 for (String key : source.mFields.keySet()) { 111 mFields.put(key, source.mFields.get(key)); 112 } 113 } 114 115 @Override 116 public int describeContents() { 117 return 0; 118 } 119 120 @Override 121 public void writeToParcel(Parcel dest, int flags) { 122 dest.writeInt(mFields.size()); 123 for (Map.Entry<String, String> entry : mFields.entrySet()) { 124 dest.writeString(entry.getKey()); 125 dest.writeString(entry.getValue()); 126 } 127 128 writeCertificate(dest, mCaCert); 129 130 if (mClientPrivateKey != null) { 131 String algorithm = mClientPrivateKey.getAlgorithm(); 132 byte[] userKeyBytes = mClientPrivateKey.getEncoded(); 133 dest.writeInt(userKeyBytes.length); 134 dest.writeByteArray(userKeyBytes); 135 dest.writeString(algorithm); 136 } else { 137 dest.writeInt(0); 138 } 139 140 writeCertificate(dest, mClientCertificate); 141 } 142 143 private void writeCertificate(Parcel dest, X509Certificate cert) { 144 if (cert != null) { 145 try { 146 byte[] certBytes = cert.getEncoded(); 147 dest.writeInt(certBytes.length); 148 dest.writeByteArray(certBytes); 149 } catch (CertificateEncodingException e) { 150 dest.writeInt(0); 151 } 152 } else { 153 dest.writeInt(0); 154 } 155 } 156 157 public static final Creator<WifiEnterpriseConfig> CREATOR = 158 new Creator<WifiEnterpriseConfig>() { 159 public WifiEnterpriseConfig createFromParcel(Parcel in) { 160 WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig(); 161 int count = in.readInt(); 162 for (int i = 0; i < count; i++) { 163 String key = in.readString(); 164 String value = in.readString(); 165 enterpriseConfig.mFields.put(key, value); 166 } 167 168 enterpriseConfig.mCaCert = readCertificate(in); 169 170 PrivateKey userKey = null; 171 int len = in.readInt(); 172 if (len > 0) { 173 try { 174 byte[] bytes = new byte[len]; 175 in.readByteArray(bytes); 176 String algorithm = in.readString(); 177 KeyFactory keyFactory = KeyFactory.getInstance(algorithm); 178 userKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(bytes)); 179 } catch (NoSuchAlgorithmException e) { 180 userKey = null; 181 } catch (InvalidKeySpecException e) { 182 userKey = null; 183 } 184 } 185 186 enterpriseConfig.mClientPrivateKey = userKey; 187 enterpriseConfig.mClientCertificate = readCertificate(in); 188 return enterpriseConfig; 189 } 190 191 private X509Certificate readCertificate(Parcel in) { 192 X509Certificate cert = null; 193 int len = in.readInt(); 194 if (len > 0) { 195 try { 196 byte[] bytes = new byte[len]; 197 in.readByteArray(bytes); 198 CertificateFactory cFactory = CertificateFactory.getInstance("X.509"); 199 cert = (X509Certificate) cFactory 200 .generateCertificate(new ByteArrayInputStream(bytes)); 201 } catch (CertificateException e) { 202 cert = null; 203 } 204 } 205 return cert; 206 } 207 208 public WifiEnterpriseConfig[] newArray(int size) { 209 return new WifiEnterpriseConfig[size]; 210 } 211 }; 212 213 /** The Extensible Authentication Protocol method used */ 214 public static final class Eap { 215 /** No EAP method used. Represents an empty config */ 216 public static final int NONE = -1; 217 /** Protected EAP */ 218 public static final int PEAP = 0; 219 /** EAP-Transport Layer Security */ 220 public static final int TLS = 1; 221 /** EAP-Tunneled Transport Layer Security */ 222 public static final int TTLS = 2; 223 /** EAP-Password */ 224 public static final int PWD = 3; 225 /** @hide */ 226 public static final String[] strings = { "PEAP", "TLS", "TTLS", "PWD" }; 227 228 /** Prevent initialization */ 229 private Eap() {} 230 } 231 232 /** The inner authentication method used */ 233 public static final class Phase2 { 234 public static final int NONE = 0; 235 /** Password Authentication Protocol */ 236 public static final int PAP = 1; 237 /** Microsoft Challenge Handshake Authentication Protocol */ 238 public static final int MSCHAP = 2; 239 /** Microsoft Challenge Handshake Authentication Protocol v2 */ 240 public static final int MSCHAPV2 = 3; 241 /** Generic Token Card */ 242 public static final int GTC = 4; 243 private static final String PREFIX = "auth="; 244 /** @hide */ 245 public static final String[] strings = {EMPTY_VALUE, "PAP", "MSCHAP", "MSCHAPV2", "GTC" }; 246 247 /** Prevent initialization */ 248 private Phase2() {} 249 } 250 251 /** Internal use only */ 252 HashMap<String, String> getFields() { 253 return mFields; 254 } 255 256 /** Internal use only */ 257 static String[] getSupplicantKeys() { 258 return new String[] { EAP_KEY, PHASE2_KEY, IDENTITY_KEY, ANON_IDENTITY_KEY, PASSWORD_KEY, 259 CLIENT_CERT_KEY, CA_CERT_KEY, SUBJECT_MATCH_KEY, ENGINE_KEY, ENGINE_ID_KEY, 260 PRIVATE_KEY_ID_KEY }; 261 } 262 263 /** 264 * Set the EAP authentication method. 265 * @param eapMethod is one {@link Eap#PEAP}, {@link Eap#TLS}, {@link Eap#TTLS} or 266 * {@link Eap#PWD} 267 * @throws IllegalArgumentException on an invalid eap method 268 */ 269 public void setEapMethod(int eapMethod) { 270 switch (eapMethod) { 271 /** Valid methods */ 272 case Eap.PEAP: 273 case Eap.PWD: 274 case Eap.TLS: 275 case Eap.TTLS: 276 mFields.put(EAP_KEY, Eap.strings[eapMethod]); 277 mFields.put(OPP_KEY_CACHING, "1"); 278 break; 279 default: 280 throw new IllegalArgumentException("Unknown EAP method"); 281 } 282 } 283 284 /** 285 * Get the eap method. 286 * @return eap method configured 287 */ 288 public int getEapMethod() { 289 String eapMethod = mFields.get(EAP_KEY); 290 return getStringIndex(Eap.strings, eapMethod, Eap.NONE); 291 } 292 293 /** 294 * Set Phase 2 authentication method. Sets the inner authentication method to be used in 295 * phase 2 after setting up a secure channel 296 * @param phase2Method is the inner authentication method and can be one of {@link Phase2#NONE}, 297 * {@link Phase2#PAP}, {@link Phase2#MSCHAP}, {@link Phase2#MSCHAPV2}, 298 * {@link Phase2#GTC} 299 * @throws IllegalArgumentException on an invalid phase2 method 300 * 301 */ 302 public void setPhase2Method(int phase2Method) { 303 switch (phase2Method) { 304 case Phase2.NONE: 305 mFields.put(PHASE2_KEY, EMPTY_VALUE); 306 break; 307 /** Valid methods */ 308 case Phase2.PAP: 309 case Phase2.MSCHAP: 310 case Phase2.MSCHAPV2: 311 case Phase2.GTC: 312 mFields.put(PHASE2_KEY, convertToQuotedString( 313 Phase2.PREFIX + Phase2.strings[phase2Method])); 314 break; 315 default: 316 throw new IllegalArgumentException("Unknown Phase 2 method"); 317 } 318 } 319 320 /** 321 * Get the phase 2 authentication method. 322 * @return a phase 2 method defined at {@link Phase2} 323 * */ 324 public int getPhase2Method() { 325 String phase2Method = removeDoubleQuotes(mFields.get(PHASE2_KEY)); 326 // Remove auth= prefix 327 if (phase2Method.startsWith(Phase2.PREFIX)) { 328 phase2Method = phase2Method.substring(Phase2.PREFIX.length()); 329 } 330 return getStringIndex(Phase2.strings, phase2Method, Phase2.NONE); 331 } 332 333 /** 334 * Set the identity 335 * @param identity 336 */ 337 public void setIdentity(String identity) { 338 setFieldValue(IDENTITY_KEY, identity, ""); 339 } 340 341 /** 342 * Get the identity 343 * @return the identity 344 */ 345 public String getIdentity() { 346 return getFieldValue(IDENTITY_KEY, ""); 347 } 348 349 /** 350 * Set anonymous identity. This is used as the unencrypted identity with 351 * certain EAP types 352 * @param anonymousIdentity the anonymous identity 353 */ 354 public void setAnonymousIdentity(String anonymousIdentity) { 355 setFieldValue(ANON_IDENTITY_KEY, anonymousIdentity, ""); 356 } 357 358 /** Get the anonymous identity 359 * @return anonymous identity 360 */ 361 public String getAnonymousIdentity() { 362 return getFieldValue(ANON_IDENTITY_KEY, ""); 363 } 364 365 /** 366 * Set the password. 367 * @param password the password 368 */ 369 public void setPassword(String password) { 370 setFieldValue(PASSWORD_KEY, password, ""); 371 } 372 373 /** 374 * Get the password. 375 * 376 * Returns locally set password value. For networks fetched from 377 * framework, returns "*". 378 */ 379 public String getPassword() { 380 return getFieldValue(PASSWORD_KEY, ""); 381 } 382 383 /** 384 * Set CA certificate alias. 385 * 386 * <p> See the {@link android.security.KeyChain} for details on installing or choosing 387 * a certificate 388 * </p> 389 * @param alias identifies the certificate 390 * @hide 391 */ 392 public void setCaCertificateAlias(String alias) { 393 setFieldValue(CA_CERT_KEY, alias, CA_CERT_PREFIX); 394 } 395 396 /** 397 * Get CA certificate alias 398 * @return alias to the CA certificate 399 * @hide 400 */ 401 public String getCaCertificateAlias() { 402 return getFieldValue(CA_CERT_KEY, CA_CERT_PREFIX); 403 } 404 405 /** 406 * Specify a X.509 certificate that identifies the server. 407 * 408 * <p>A default name is automatically assigned to the certificate and used 409 * with this configuration. The framework takes care of installing the 410 * certificate when the config is saved and removing the certificate when 411 * the config is removed. 412 * 413 * @param cert X.509 CA certificate 414 * @throws IllegalArgumentException if not a CA certificate 415 */ 416 public void setCaCertificate(X509Certificate cert) { 417 if (cert != null) { 418 if (cert.getBasicConstraints() >= 0) { 419 mCaCert = cert; 420 } else { 421 throw new IllegalArgumentException("Not a CA certificate"); 422 } 423 } else { 424 mCaCert = null; 425 } 426 } 427 428 /** 429 * Get CA certificate 430 * 431 * @return X.509 CA certificate 432 */ 433 public X509Certificate getCaCertificate() { 434 return mCaCert; 435 } 436 437 /** 438 * Set Client certificate alias. 439 * 440 * <p> See the {@link android.security.KeyChain} for details on installing or choosing 441 * a certificate 442 * </p> 443 * @param alias identifies the certificate 444 * @hide 445 */ 446 public void setClientCertificateAlias(String alias) { 447 setFieldValue(CLIENT_CERT_KEY, alias, CLIENT_CERT_PREFIX); 448 setFieldValue(PRIVATE_KEY_ID_KEY, alias, Credentials.USER_PRIVATE_KEY); 449 // Also, set engine parameters 450 if (TextUtils.isEmpty(alias)) { 451 mFields.put(ENGINE_KEY, ENGINE_DISABLE); 452 mFields.put(ENGINE_ID_KEY, EMPTY_VALUE); 453 } else { 454 mFields.put(ENGINE_KEY, ENGINE_ENABLE); 455 mFields.put(ENGINE_ID_KEY, convertToQuotedString(ENGINE_ID_KEYSTORE)); 456 } 457 } 458 459 /** 460 * Get client certificate alias 461 * @return alias to the client certificate 462 * @hide 463 */ 464 public String getClientCertificateAlias() { 465 return getFieldValue(CLIENT_CERT_KEY, CLIENT_CERT_PREFIX); 466 } 467 468 /** 469 * Specify a private key and client certificate for client authorization. 470 * 471 * <p>A default name is automatically assigned to the key entry and used 472 * with this configuration. The framework takes care of installing the 473 * key entry when the config is saved and removing the key entry when 474 * the config is removed. 475 476 * @param privateKey 477 * @param clientCertificate 478 * @throws IllegalArgumentException for an invalid key or certificate. 479 */ 480 public void setClientKeyEntry(PrivateKey privateKey, X509Certificate clientCertificate) { 481 if (clientCertificate != null) { 482 if (clientCertificate.getBasicConstraints() != -1) { 483 throw new IllegalArgumentException("Cannot be a CA certificate"); 484 } 485 if (privateKey == null) { 486 throw new IllegalArgumentException("Client cert without a private key"); 487 } 488 if (privateKey.getEncoded() == null) { 489 throw new IllegalArgumentException("Private key cannot be encoded"); 490 } 491 } 492 493 mClientPrivateKey = privateKey; 494 mClientCertificate = clientCertificate; 495 } 496 497 /** 498 * Get client certificate 499 * 500 * @return X.509 client certificate 501 */ 502 public X509Certificate getClientCertificate() { 503 return mClientCertificate; 504 } 505 506 boolean needsKeyStore() { 507 // Has no keys to be installed 508 if (mClientCertificate == null && mCaCert == null) return false; 509 return true; 510 } 511 512 boolean installKeys(android.security.KeyStore keyStore, String name) { 513 boolean ret = true; 514 String privKeyName = Credentials.USER_PRIVATE_KEY + name; 515 String userCertName = Credentials.USER_CERTIFICATE + name; 516 String caCertName = Credentials.CA_CERTIFICATE + name; 517 if (mClientCertificate != null) { 518 byte[] privKeyData = mClientPrivateKey.getEncoded(); 519 ret = keyStore.importKey(privKeyName, privKeyData, Process.WIFI_UID, 520 KeyStore.FLAG_ENCRYPTED); 521 if (ret == false) { 522 return ret; 523 } 524 525 ret = putCertInKeyStore(keyStore, userCertName, mClientCertificate); 526 if (ret == false) { 527 // Remove private key installed 528 keyStore.delKey(privKeyName, Process.WIFI_UID); 529 return ret; 530 } 531 } 532 533 if (mCaCert != null) { 534 ret = putCertInKeyStore(keyStore, caCertName, mCaCert); 535 if (ret == false) { 536 if (mClientCertificate != null) { 537 // Remove client key+cert 538 keyStore.delKey(privKeyName, Process.WIFI_UID); 539 keyStore.delete(userCertName, Process.WIFI_UID); 540 } 541 return ret; 542 } 543 } 544 545 // Set alias names 546 if (mClientCertificate != null) { 547 setClientCertificateAlias(name); 548 mClientPrivateKey = null; 549 mClientCertificate = null; 550 } 551 552 if (mCaCert != null) { 553 setCaCertificateAlias(name); 554 mCaCert = null; 555 } 556 557 return ret; 558 } 559 560 private boolean putCertInKeyStore(android.security.KeyStore keyStore, String name, 561 Certificate cert) { 562 try { 563 byte[] certData = Credentials.convertToPem(cert); 564 return keyStore.put(name, certData, Process.WIFI_UID, KeyStore.FLAG_ENCRYPTED); 565 } catch (IOException e1) { 566 return false; 567 } catch (CertificateException e2) { 568 return false; 569 } 570 } 571 572 void removeKeys(KeyStore keyStore) { 573 String client = getFieldValue(CLIENT_CERT_KEY, CLIENT_CERT_PREFIX); 574 // a valid client certificate is configured 575 if (!TextUtils.isEmpty(client)) { 576 keyStore.delKey(Credentials.USER_PRIVATE_KEY + client, Process.WIFI_UID); 577 keyStore.delete(Credentials.USER_CERTIFICATE + client, Process.WIFI_UID); 578 } 579 580 String ca = getFieldValue(CA_CERT_KEY, CA_CERT_PREFIX); 581 // a valid ca certificate is configured 582 if (!TextUtils.isEmpty(ca)) { 583 keyStore.delete(Credentials.CA_CERTIFICATE + ca, Process.WIFI_UID); 584 } 585 } 586 587 /** 588 * Set subject match. This is the substring to be matched against the subject of the 589 * authentication server certificate. 590 * @param subjectMatch substring to be matched 591 */ 592 public void setSubjectMatch(String subjectMatch) { 593 setFieldValue(SUBJECT_MATCH_KEY, subjectMatch, ""); 594 } 595 596 /** 597 * Get subject match 598 * @return the subject match string 599 */ 600 public String getSubjectMatch() { 601 return getFieldValue(SUBJECT_MATCH_KEY, ""); 602 } 603 604 /** See {@link WifiConfiguration#getKeyIdForCredentials} @hide */ 605 String getKeyId(WifiEnterpriseConfig current) { 606 String eap = mFields.get(EAP_KEY); 607 String phase2 = mFields.get(PHASE2_KEY); 608 609 // If either eap or phase2 are not initialized, use current config details 610 if (TextUtils.isEmpty((eap))) { 611 eap = current.mFields.get(EAP_KEY); 612 } 613 if (TextUtils.isEmpty(phase2)) { 614 phase2 = current.mFields.get(PHASE2_KEY); 615 } 616 return eap + "_" + phase2; 617 } 618 619 /** Migrates the old style TLS config to the new config style. This should only be used 620 * when restoring an old wpa_supplicant.conf or upgrading from a previous 621 * platform version. 622 * @return true if the config was updated 623 * @hide 624 */ 625 boolean migrateOldEapTlsNative(WifiNative wifiNative, int netId) { 626 String oldPrivateKey = wifiNative.getNetworkVariable(netId, OLD_PRIVATE_KEY_NAME); 627 /* 628 * If the old configuration value is not present, then there is nothing 629 * to do. 630 */ 631 if (TextUtils.isEmpty(oldPrivateKey)) { 632 return false; 633 } else { 634 // Also ignore it if it's empty quotes. 635 oldPrivateKey = removeDoubleQuotes(oldPrivateKey); 636 if (TextUtils.isEmpty(oldPrivateKey)) { 637 return false; 638 } 639 } 640 641 mFields.put(ENGINE_KEY, ENGINE_ENABLE); 642 mFields.put(ENGINE_ID_KEY, convertToQuotedString(ENGINE_ID_KEYSTORE)); 643 644 /* 645 * The old key started with the keystore:// URI prefix, but we don't 646 * need that anymore. Trim it off if it exists. 647 */ 648 final String keyName; 649 if (oldPrivateKey.startsWith(KEYSTORE_URI)) { 650 keyName = new String(oldPrivateKey.substring(KEYSTORE_URI.length())); 651 } else { 652 keyName = oldPrivateKey; 653 } 654 mFields.put(PRIVATE_KEY_ID_KEY, convertToQuotedString(keyName)); 655 656 wifiNative.setNetworkVariable(netId, ENGINE_KEY, mFields.get(ENGINE_KEY)); 657 wifiNative.setNetworkVariable(netId, ENGINE_ID_KEY, mFields.get(ENGINE_ID_KEY)); 658 wifiNative.setNetworkVariable(netId, PRIVATE_KEY_ID_KEY, mFields.get(PRIVATE_KEY_ID_KEY)); 659 // Remove old private_key string so we don't run this again. 660 wifiNative.setNetworkVariable(netId, OLD_PRIVATE_KEY_NAME, EMPTY_VALUE); 661 return true; 662 } 663 664 /** Migrate certs from global pool to wifi UID if not already done */ 665 void migrateCerts(android.security.KeyStore keyStore) { 666 String client = getFieldValue(CLIENT_CERT_KEY, CLIENT_CERT_PREFIX); 667 // a valid client certificate is configured 668 if (!TextUtils.isEmpty(client)) { 669 if (!keyStore.contains(Credentials.USER_PRIVATE_KEY + client, Process.WIFI_UID)) { 670 keyStore.duplicate(Credentials.USER_PRIVATE_KEY + client, -1, 671 Credentials.USER_PRIVATE_KEY + client, Process.WIFI_UID); 672 keyStore.duplicate(Credentials.USER_CERTIFICATE + client, -1, 673 Credentials.USER_CERTIFICATE + client, Process.WIFI_UID); 674 } 675 } 676 677 String ca = getFieldValue(CA_CERT_KEY, CA_CERT_PREFIX); 678 // a valid ca certificate is configured 679 if (!TextUtils.isEmpty(ca)) { 680 if (!keyStore.contains(Credentials.CA_CERTIFICATE + ca, Process.WIFI_UID)) { 681 keyStore.duplicate(Credentials.CA_CERTIFICATE + ca, -1, 682 Credentials.CA_CERTIFICATE + ca, Process.WIFI_UID); 683 } 684 } 685 } 686 687 private String removeDoubleQuotes(String string) { 688 if (TextUtils.isEmpty(string)) return ""; 689 int length = string.length(); 690 if ((length > 1) && (string.charAt(0) == '"') 691 && (string.charAt(length - 1) == '"')) { 692 return string.substring(1, length - 1); 693 } 694 return string; 695 } 696 697 private String convertToQuotedString(String string) { 698 return "\"" + string + "\""; 699 } 700 701 /** Returns the index at which the toBeFound string is found in the array. 702 * @param arr array of strings 703 * @param toBeFound string to be found 704 * @param defaultIndex default index to be returned when string is not found 705 * @return the index into array 706 */ 707 private int getStringIndex(String arr[], String toBeFound, int defaultIndex) { 708 if (TextUtils.isEmpty(toBeFound)) return defaultIndex; 709 for (int i = 0; i < arr.length; i++) { 710 if (toBeFound.equals(arr[i])) return i; 711 } 712 return defaultIndex; 713 } 714 715 /** Returns the field value for the key. 716 * @param key into the hash 717 * @param prefix is the prefix that the value may have 718 * @return value 719 */ 720 private String getFieldValue(String key, String prefix) { 721 String value = mFields.get(key); 722 // Uninitialized or known to be empty after reading from supplicant 723 if (TextUtils.isEmpty(value) || EMPTY_VALUE.equals(value)) return ""; 724 return removeDoubleQuotes(value).substring(prefix.length()); 725 } 726 727 /** Set a value with an optional prefix at key 728 * @param key into the hash 729 * @param value to be set 730 * @param prefix an optional value to be prefixed to actual value 731 */ 732 private void setFieldValue(String key, String value, String prefix) { 733 if (TextUtils.isEmpty(value)) { 734 mFields.put(key, EMPTY_VALUE); 735 } else { 736 mFields.put(key, convertToQuotedString(prefix + value)); 737 } 738 } 739 740 @Override 741 public String toString() { 742 StringBuffer sb = new StringBuffer(); 743 for (String key : mFields.keySet()) { 744 sb.append(key).append(" ").append(mFields.get(key)).append("\n"); 745 } 746 return sb.toString(); 747 } 748} 749