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