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