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