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