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