WifiEnterpriseConfig.java revision 7c28c3663470ce5eb818cfa2ce4a993e4bae8f33
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.security.Credentials; 21import android.text.TextUtils; 22 23import java.io.ByteArrayInputStream; 24import java.security.KeyFactory; 25import java.security.NoSuchAlgorithmException; 26import java.security.PrivateKey; 27import java.security.cert.CertificateEncodingException; 28import java.security.cert.CertificateException; 29import java.security.cert.CertificateFactory; 30import java.security.cert.X509Certificate; 31import java.security.spec.InvalidKeySpecException; 32import java.security.spec.PKCS8EncodedKeySpec; 33import java.util.HashMap; 34import java.util.Map; 35 36/** 37 * Enterprise configuration details for Wi-Fi. Stores details about the EAP method 38 * and any associated credentials. 39 */ 40public class WifiEnterpriseConfig implements Parcelable { 41 42 /** @hide */ 43 public static final String EMPTY_VALUE = "NULL"; 44 /** @hide */ 45 public static final String EAP_KEY = "eap"; 46 /** @hide */ 47 public static final String PHASE2_KEY = "phase2"; 48 /** @hide */ 49 public static final String IDENTITY_KEY = "identity"; 50 /** @hide */ 51 public static final String ANON_IDENTITY_KEY = "anonymous_identity"; 52 /** @hide */ 53 public static final String PASSWORD_KEY = "password"; 54 /** @hide */ 55 public static final String SUBJECT_MATCH_KEY = "subject_match"; 56 /** @hide */ 57 public static final String OPP_KEY_CACHING = "proactive_key_caching"; 58 /** 59 * String representing the keystore OpenSSL ENGINE's ID. 60 * @hide 61 */ 62 public static final String ENGINE_ID_KEYSTORE = "keystore"; 63 64 /** 65 * String representing the keystore URI used for wpa_supplicant. 66 * @hide 67 */ 68 public static final String KEYSTORE_URI = "keystore://"; 69 70 /** 71 * String to set the engine value to when it should be enabled. 72 * @hide 73 */ 74 public static final String ENGINE_ENABLE = "1"; 75 76 /** 77 * String to set the engine value to when it should be disabled. 78 * @hide 79 */ 80 public static final String ENGINE_DISABLE = "0"; 81 82 /** @hide */ 83 public static final String CA_CERT_PREFIX = KEYSTORE_URI + Credentials.CA_CERTIFICATE; 84 /** @hide */ 85 public static final String CLIENT_CERT_PREFIX = KEYSTORE_URI + Credentials.USER_CERTIFICATE; 86 /** @hide */ 87 public static final String CLIENT_CERT_KEY = "client_cert"; 88 /** @hide */ 89 public static final String CA_CERT_KEY = "ca_cert"; 90 /** @hide */ 91 public static final String ENGINE_KEY = "engine"; 92 /** @hide */ 93 public static final String ENGINE_ID_KEY = "engine_id"; 94 /** @hide */ 95 public static final String PRIVATE_KEY_ID_KEY = "key_id"; 96 97 private HashMap<String, String> mFields = new HashMap<String, String>(); 98 private X509Certificate mCaCert; 99 private PrivateKey mClientPrivateKey; 100 private X509Certificate mClientCertificate; 101 102 public WifiEnterpriseConfig() { 103 // Do not set defaults so that the enterprise fields that are not changed 104 // by API are not changed underneath 105 // This is essential because an app may not have all fields like password 106 // available. It allows modification of subset of fields. 107 108 } 109 110 /** Copy constructor */ 111 public WifiEnterpriseConfig(WifiEnterpriseConfig source) { 112 for (String key : source.mFields.keySet()) { 113 mFields.put(key, source.mFields.get(key)); 114 } 115 } 116 117 @Override 118 public int describeContents() { 119 return 0; 120 } 121 122 @Override 123 public void writeToParcel(Parcel dest, int flags) { 124 dest.writeInt(mFields.size()); 125 for (Map.Entry<String, String> entry : mFields.entrySet()) { 126 dest.writeString(entry.getKey()); 127 dest.writeString(entry.getValue()); 128 } 129 130 writeCertificate(dest, mCaCert); 131 132 if (mClientPrivateKey != null) { 133 String algorithm = mClientPrivateKey.getAlgorithm(); 134 byte[] userKeyBytes = mClientPrivateKey.getEncoded(); 135 dest.writeInt(userKeyBytes.length); 136 dest.writeByteArray(userKeyBytes); 137 dest.writeString(algorithm); 138 } else { 139 dest.writeInt(0); 140 } 141 142 writeCertificate(dest, mClientCertificate); 143 } 144 145 private void writeCertificate(Parcel dest, X509Certificate cert) { 146 if (cert != null) { 147 try { 148 byte[] certBytes = cert.getEncoded(); 149 dest.writeInt(certBytes.length); 150 dest.writeByteArray(certBytes); 151 } catch (CertificateEncodingException e) { 152 dest.writeInt(0); 153 } 154 } else { 155 dest.writeInt(0); 156 } 157 } 158 159 public static final Creator<WifiEnterpriseConfig> CREATOR = 160 new Creator<WifiEnterpriseConfig>() { 161 public WifiEnterpriseConfig createFromParcel(Parcel in) { 162 WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig(); 163 int count = in.readInt(); 164 for (int i = 0; i < count; i++) { 165 String key = in.readString(); 166 String value = in.readString(); 167 enterpriseConfig.mFields.put(key, value); 168 } 169 170 enterpriseConfig.mCaCert = readCertificate(in); 171 172 PrivateKey userKey = null; 173 int len = in.readInt(); 174 if (len > 0) { 175 try { 176 byte[] bytes = new byte[len]; 177 in.readByteArray(bytes); 178 String algorithm = in.readString(); 179 KeyFactory keyFactory = KeyFactory.getInstance(algorithm); 180 userKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(bytes)); 181 } catch (NoSuchAlgorithmException e) { 182 userKey = null; 183 } catch (InvalidKeySpecException e) { 184 userKey = null; 185 } 186 } 187 188 enterpriseConfig.mClientPrivateKey = userKey; 189 enterpriseConfig.mClientCertificate = readCertificate(in); 190 return enterpriseConfig; 191 } 192 193 private X509Certificate readCertificate(Parcel in) { 194 X509Certificate cert = null; 195 int len = in.readInt(); 196 if (len > 0) { 197 try { 198 byte[] bytes = new byte[len]; 199 in.readByteArray(bytes); 200 CertificateFactory cFactory = CertificateFactory.getInstance("X.509"); 201 cert = (X509Certificate) cFactory 202 .generateCertificate(new ByteArrayInputStream(bytes)); 203 } catch (CertificateException e) { 204 cert = null; 205 } 206 } 207 return cert; 208 } 209 210 public WifiEnterpriseConfig[] newArray(int size) { 211 return new WifiEnterpriseConfig[size]; 212 } 213 }; 214 215 /** The Extensible Authentication Protocol method used */ 216 public static final class Eap { 217 /** No EAP method used. Represents an empty config */ 218 public static final int NONE = -1; 219 /** Protected EAP */ 220 public static final int PEAP = 0; 221 /** EAP-Transport Layer Security */ 222 public static final int TLS = 1; 223 /** EAP-Tunneled Transport Layer Security */ 224 public static final int TTLS = 2; 225 /** EAP-Password */ 226 public static final int PWD = 3; 227 /** EAP-Subscriber Identity Module {@hide} */ 228 public static final int SIM = 4; 229 /** EAP-Authentication and Key Agreement {@hide} */ 230 public static final int AKA = 5; 231 /** @hide */ 232 public static final String[] strings = { "PEAP", "TLS", "TTLS", "PWD", "SIM", "AKA" }; 233 234 /** Prevent initialization */ 235 private Eap() {} 236 } 237 238 /** The inner authentication method used */ 239 public static final class Phase2 { 240 public static final int NONE = 0; 241 /** Password Authentication Protocol */ 242 public static final int PAP = 1; 243 /** Microsoft Challenge Handshake Authentication Protocol */ 244 public static final int MSCHAP = 2; 245 /** Microsoft Challenge Handshake Authentication Protocol v2 */ 246 public static final int MSCHAPV2 = 3; 247 /** Generic Token Card */ 248 public static final int GTC = 4; 249 private static final String PREFIX = "auth="; 250 /** @hide */ 251 public static final String[] strings = {EMPTY_VALUE, "PAP", "MSCHAP", 252 "MSCHAPV2", "GTC" }; 253 254 /** Prevent initialization */ 255 private Phase2() {} 256 } 257 258 /** Internal use only 259 * @hide 260 */ 261 public HashMap<String, String> getFields() { 262 return mFields; 263 } 264 265 /** 266 * Set the EAP authentication method. 267 * @param eapMethod is one {@link Eap#PEAP}, {@link Eap#TLS}, {@link Eap#TTLS} or 268 * {@link Eap#PWD} 269 * @throws IllegalArgumentException on an invalid eap method 270 */ 271 public void setEapMethod(int eapMethod) { 272 switch (eapMethod) { 273 /** Valid methods */ 274 case Eap.PEAP: 275 case Eap.PWD: 276 case Eap.TLS: 277 case Eap.TTLS: 278 case Eap.SIM: 279 case Eap.AKA: 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 * @return X.509 CA certificate 435 */ 436 public X509Certificate getCaCertificate() { 437 return mCaCert; 438 } 439 440 /** 441 * @hide 442 */ 443 public void resetCaCertificate() { 444 mCaCert = null; 445 } 446 447 /** Set Client certificate alias. 448 * 449 * <p> See the {@link android.security.KeyChain} for details on installing or choosing 450 * a certificate 451 * </p> 452 * @param alias identifies the certificate 453 * @hide 454 */ 455 public void setClientCertificateAlias(String alias) { 456 setFieldValue(CLIENT_CERT_KEY, alias, CLIENT_CERT_PREFIX); 457 setFieldValue(PRIVATE_KEY_ID_KEY, alias, Credentials.USER_PRIVATE_KEY); 458 // Also, set engine parameters 459 if (TextUtils.isEmpty(alias)) { 460 mFields.put(ENGINE_KEY, ENGINE_DISABLE); 461 mFields.put(ENGINE_ID_KEY, EMPTY_VALUE); 462 } else { 463 mFields.put(ENGINE_KEY, ENGINE_ENABLE); 464 mFields.put(ENGINE_ID_KEY, convertToQuotedString(ENGINE_ID_KEYSTORE)); 465 } 466 } 467 468 /** 469 * Get client certificate alias 470 * @return alias to the client certificate 471 * @hide 472 */ 473 public String getClientCertificateAlias() { 474 return getFieldValue(CLIENT_CERT_KEY, CLIENT_CERT_PREFIX); 475 } 476 477 /** 478 * Specify a private key and client certificate for client authorization. 479 * 480 * <p>A default name is automatically assigned to the key entry and used 481 * with this configuration. The framework takes care of installing the 482 * key entry when the config is saved and removing the key entry when 483 * the config is removed. 484 485 * @param privateKey 486 * @param clientCertificate 487 * @throws IllegalArgumentException for an invalid key or certificate. 488 */ 489 public void setClientKeyEntry(PrivateKey privateKey, X509Certificate clientCertificate) { 490 if (clientCertificate != null) { 491 if (clientCertificate.getBasicConstraints() != -1) { 492 throw new IllegalArgumentException("Cannot be a CA certificate"); 493 } 494 if (privateKey == null) { 495 throw new IllegalArgumentException("Client cert without a private key"); 496 } 497 if (privateKey.getEncoded() == null) { 498 throw new IllegalArgumentException("Private key cannot be encoded"); 499 } 500 } 501 502 mClientPrivateKey = privateKey; 503 mClientCertificate = clientCertificate; 504 } 505 506 /** 507 * Get client certificate 508 * 509 * @return X.509 client certificate 510 */ 511 public X509Certificate getClientCertificate() { 512 return mClientCertificate; 513 } 514 515 /** 516 * @hide 517 */ 518 public void resetClientKeyEntry() { 519 mClientPrivateKey = null; 520 mClientCertificate = null; 521 } 522 523 /** 524 * @hide 525 */ 526 public PrivateKey getClientPrivateKey() { 527 return mClientPrivateKey; 528 } 529 530 /** 531 * Set subject match. This is the substring to be matched against the subject of the 532 * authentication server certificate. 533 * @param subjectMatch substring to be matched 534 */ 535 public void setSubjectMatch(String subjectMatch) { 536 setFieldValue(SUBJECT_MATCH_KEY, subjectMatch, ""); 537 } 538 539 /** 540 * Get subject match 541 * @return the subject match string 542 */ 543 public String getSubjectMatch() { 544 return getFieldValue(SUBJECT_MATCH_KEY, ""); 545 } 546 547 /** See {@link WifiConfiguration#getKeyIdForCredentials} @hide */ 548 String getKeyId(WifiEnterpriseConfig current) { 549 String eap = mFields.get(EAP_KEY); 550 String phase2 = mFields.get(PHASE2_KEY); 551 552 // If either eap or phase2 are not initialized, use current config details 553 if (TextUtils.isEmpty((eap))) { 554 eap = current.mFields.get(EAP_KEY); 555 } 556 if (TextUtils.isEmpty(phase2)) { 557 phase2 = current.mFields.get(PHASE2_KEY); 558 } 559 return eap + "_" + phase2; 560 } 561 562 private String removeDoubleQuotes(String string) { 563 if (TextUtils.isEmpty(string)) return ""; 564 int length = string.length(); 565 if ((length > 1) && (string.charAt(0) == '"') 566 && (string.charAt(length - 1) == '"')) { 567 return string.substring(1, length - 1); 568 } 569 return string; 570 } 571 572 private String convertToQuotedString(String string) { 573 return "\"" + string + "\""; 574 } 575 576 /** Returns the index at which the toBeFound string is found in the array. 577 * @param arr array of strings 578 * @param toBeFound string to be found 579 * @param defaultIndex default index to be returned when string is not found 580 * @return the index into array 581 */ 582 private int getStringIndex(String arr[], String toBeFound, int defaultIndex) { 583 if (TextUtils.isEmpty(toBeFound)) return defaultIndex; 584 for (int i = 0; i < arr.length; i++) { 585 if (toBeFound.equals(arr[i])) return i; 586 } 587 return defaultIndex; 588 } 589 590 /** Returns the field value for the key. 591 * @param key into the hash 592 * @param prefix is the prefix that the value may have 593 * @return value 594 * @hide 595 */ 596 public String getFieldValue(String key, String prefix) { 597 String value = mFields.get(key); 598 // Uninitialized or known to be empty after reading from supplicant 599 if (TextUtils.isEmpty(value) || EMPTY_VALUE.equals(value)) return ""; 600 601 value = removeDoubleQuotes(value); 602 if (value.startsWith(prefix)) { 603 return value.substring(prefix.length()); 604 } else { 605 return value; 606 } 607 } 608 609 /** Set a value with an optional prefix at key 610 * @param key into the hash 611 * @param value to be set 612 * @param prefix an optional value to be prefixed to actual value 613 * @hide 614 */ 615 public void setFieldValue(String key, String value, String prefix) { 616 if (TextUtils.isEmpty(value)) { 617 mFields.put(key, EMPTY_VALUE); 618 } else { 619 mFields.put(key, convertToQuotedString(prefix + value)); 620 } 621 } 622 623 624 /** Set a value with an optional prefix at key 625 * @param key into the hash 626 * @param value to be set 627 * @param prefix an optional value to be prefixed to actual value 628 * @hide 629 */ 630 public void setFieldValue(String key, String value) { 631 if (TextUtils.isEmpty(value)) { 632 mFields.put(key, EMPTY_VALUE); 633 } else { 634 mFields.put(key, convertToQuotedString(value)); 635 } 636 } 637 638 @Override 639 public String toString() { 640 StringBuffer sb = new StringBuffer(); 641 for (String key : mFields.keySet()) { 642 sb.append(key).append(" ").append(mFields.get(key)).append("\n"); 643 } 644 return sb.toString(); 645 } 646} 647