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