Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 26d0076f authored by Irfan Sheriff's avatar Irfan Sheriff
Browse files

Track keys per config and allow cert push from apps

Allow configuring keys for a configuration and update/delete
from wificonfigstore.

Change-Id: I79b43bf7ca58f7efc95f7dcc125fc84d7aa8c795
parent 080df3f3
Loading
Loading
Loading
Loading
+54 −19
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import android.os.Message;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.UserHandle;
import android.security.KeyStore;
import android.text.TextUtils;
import android.util.Log;

@@ -144,6 +145,7 @@ class WifiConfigStore {
    private static final String EOS = "eos";

    private WifiNative mWifiNative;
    private final KeyStore mKeyStore = KeyStore.getInstance();

    WifiConfigStore(Context c, WifiNative wn) {
        mContext = c;
@@ -295,16 +297,7 @@ class WifiConfigStore {
    boolean forgetNetwork(int netId) {
        if (mWifiNative.removeNetwork(netId)) {
            mWifiNative.saveConfig();
            WifiConfiguration target = null;
            WifiConfiguration config = mConfiguredNetworks.get(netId);
            if (config != null) {
                target = mConfiguredNetworks.remove(netId);
                mNetworkIds.remove(configKey(config));
            }
            if (target != null) {
                writeIpAndProxyConfigurations();
                sendConfiguredNetworksChangedBroadcast(target, WifiManager.CHANGE_REASON_REMOVED);
            }
            removeConfigAndSendBroadcastIfNeeded(netId);
            return true;
        } else {
            loge("Failed to remove network " + netId);
@@ -342,18 +335,25 @@ class WifiConfigStore {
     */
    boolean removeNetwork(int netId) {
        boolean ret = mWifiNative.removeNetwork(netId);
        WifiConfiguration config = null;
        if (ret) {
            config = mConfiguredNetworks.get(netId);
            if (config != null) {
                config = mConfiguredNetworks.remove(netId);
                mNetworkIds.remove(configKey(config));
            removeConfigAndSendBroadcastIfNeeded(netId);
        }
        return ret;
    }

    private void removeConfigAndSendBroadcastIfNeeded(int netId) {
        WifiConfiguration config = mConfiguredNetworks.get(netId);
        if (config != null) {
            // Remove any associated keys
            if (config.enterpriseConfig != null) {
                config.enterpriseConfig.removeKeys(mKeyStore);
            }
            mConfiguredNetworks.remove(netId);
            mNetworkIds.remove(configKey(config));

            writeIpAndProxyConfigurations();
            sendConfiguredNetworksChangedBroadcast(config, WifiManager.CHANGE_REASON_REMOVED);
        }
        return ret;
    }

    /**
@@ -1090,13 +1090,48 @@ class WifiConfigStore {
            }

            if (config.enterpriseConfig != null) {
                HashMap<String, String> enterpriseFields = config.enterpriseConfig.getFields();

                WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;

                if (enterpriseConfig.needsKeyStore()) {
                    /**
                     * Keyguard settings may eventually be controlled by device policy.
                     * We check here if keystore is unlocked before installing
                     * credentials.
                     * TODO: Figure a way to store these credentials for wifi alone
                     * TODO: Do we need a dialog here ?
                     */
                    if (mKeyStore.state() != KeyStore.State.UNLOCKED) {
                        loge(config.SSID + ": key store is locked");
                        break setVariables;
                    }

                    try {
                        /* config passed may include only fields being updated.
                         * In order to generate the key id, fetch uninitialized
                         * fields from the currently tracked configuration
                         */
                        WifiConfiguration currentConfig = mConfiguredNetworks.get(netId);
                        String keyId = config.getKeyIdForCredentials(currentConfig);

                        if (!enterpriseConfig.installKeys(mKeyStore, keyId)) {
                            loge(config.SSID + ": failed to install keys");
                            break setVariables;
                        }
                    } catch (IllegalStateException e) {
                        loge(config.SSID + " invalid config for key installation");
                        break setVariables;
                    }
                }

                HashMap<String, String> enterpriseFields = enterpriseConfig.getFields();
                for (String key : enterpriseFields.keySet()) {
                        String value = enterpriseFields.get(key);
                        if (!mWifiNative.setNetworkVariable(
                                    netId,
                                    key,
                                    value)) {
                            enterpriseConfig.removeKeys(mKeyStore);
                            loge(config.SSID + ": failed to set " + key +
                                    ": " + value);
                            break setVariables;
@@ -1104,7 +1139,7 @@ class WifiConfigStore {
                }
            }
            updateFailed = false;
        }
        } //end of setVariables

        if (updateFailed) {
            if (newNetwork) {
+47 −1
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package android.net.wifi;
import android.net.LinkProperties;
import android.os.Parcelable;
import android.os.Parcel;
import android.text.TextUtils;

import java.util.BitSet;

@@ -274,7 +275,8 @@ public class WifiConfiguration implements Parcelable {
     */
    public BitSet allowedGroupCiphers;
    /**
     * The enterprise configuration details
     * The enterprise configuration details specifying the EAP method,
     * certificates and other settings associated with the EAP.
     * @hide
     */
    public WifiEnterpriseConfig enterpriseConfig;
@@ -462,6 +464,47 @@ public class WifiConfiguration implements Parcelable {
        return SSID;
    }

    /**
     * Get an identifier for associating credentials with this config
     * @param current configuration contains values for additional fields
     *                that are not part of this configuration. Used
     *                when a config with some fields is passed by an application.
     * @throws IllegalStateException if config is invalid for key id generation
     * @hide
     */
    String getKeyIdForCredentials(WifiConfiguration current) {
        String keyMgmt = null;

        try {
            // Get current config details for fields that are not initialized
            if (TextUtils.isEmpty(SSID)) SSID = current.SSID;
            if (allowedKeyManagement.cardinality() == 0) {
                allowedKeyManagement = current.allowedKeyManagement;
            }
            if (allowedKeyManagement.get(KeyMgmt.WPA_EAP)) {
                keyMgmt = KeyMgmt.strings[KeyMgmt.WPA_EAP];
            }
            if (allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
                keyMgmt += KeyMgmt.strings[KeyMgmt.IEEE8021X];
            }

            if (TextUtils.isEmpty(keyMgmt)) {
                throw new IllegalStateException("Not an EAP network");
            }

            return trimStringForKeyId(SSID) + "_" + keyMgmt + "_" +
                    trimStringForKeyId(enterpriseConfig.getKeyId(current != null ?
                            current.enterpriseConfig : null));
        } catch (NullPointerException e) {
            throw new IllegalStateException("Invalid config details");
        }
    }

    private String trimStringForKeyId(String string) {
        // Remove quotes and spaces
        return string.replace("\"", "").replace(" ", "");
    }

    private static BitSet readBitSet(Parcel src) {
        int cardinality = src.readInt();

@@ -485,6 +528,9 @@ public class WifiConfiguration implements Parcelable {

    /** @hide */
    public int getAuthType() {
        if (allowedKeyManagement.cardinality() > 1) {
            throw new IllegalStateException("More than one auth type set");
        }
        if (allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
            return KeyMgmt.WPA_PSK;
        } else if (allowedKeyManagement.get(KeyMgmt.WPA2_PSK)) {
+241 −11
Original line number Diff line number Diff line
@@ -19,7 +19,26 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.security.Credentials;
import android.text.TextUtils;

import android.util.Log;

import com.android.org.bouncycastle.asn1.ASN1InputStream;
import com.android.org.bouncycastle.asn1.ASN1Sequence;
import com.android.org.bouncycastle.asn1.DEROctetString;
import com.android.org.bouncycastle.asn1.x509.BasicConstraints;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

@@ -70,6 +89,9 @@ public class WifiEnterpriseConfig implements Parcelable {
    private static final String PRIVATE_KEY_ID_KEY  = "key_id";

    private HashMap<String, String> mFields = new HashMap<String, String>();
    private X509Certificate mCaCert;
    private PrivateKey mClientPrivateKey;
    private X509Certificate mClientCertificate;

    /** This represents an empty value of an enterprise field.
     * NULL is used at wpa_supplicant to indicate an empty value
@@ -103,6 +125,34 @@ public class WifiEnterpriseConfig implements Parcelable {
            dest.writeString(entry.getKey());
            dest.writeString(entry.getValue());
        }

        writeCertificate(dest, mCaCert);

        if (mClientPrivateKey != null) {
            String algorithm = mClientPrivateKey.getAlgorithm();
            byte[] userKeyBytes = mClientPrivateKey.getEncoded();
            dest.writeInt(userKeyBytes.length);
            dest.writeByteArray(userKeyBytes);
            dest.writeString(algorithm);
        } else {
            dest.writeInt(0);
        }

        writeCertificate(dest, mClientCertificate);
    }

    private void writeCertificate(Parcel dest, X509Certificate cert) {
        if (cert != null) {
            try {
                byte[] certBytes = cert.getEncoded();
                dest.writeInt(certBytes.length);
                dest.writeByteArray(certBytes);
            } catch (CertificateEncodingException e) {
                dest.writeInt(0);
            }
        } else {
            dest.writeInt(0);
        }
    }

    public static final Creator<WifiEnterpriseConfig> CREATOR =
@@ -115,9 +165,47 @@ public class WifiEnterpriseConfig implements Parcelable {
                        String value = in.readString();
                        enterpriseConfig.mFields.put(key, value);
                    }

                    enterpriseConfig.mCaCert = readCertificate(in);

                    PrivateKey userKey = null;
                    int len = in.readInt();
                    if (len > 0) {
                        try {
                            byte[] bytes = new byte[len];
                            in.readByteArray(bytes);
                            String algorithm = in.readString();
                            KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
                            userKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(bytes));
                        } catch (NoSuchAlgorithmException e) {
                            userKey = null;
                        } catch (InvalidKeySpecException e) {
                            userKey = null;
                        }
                    }

                    enterpriseConfig.mClientPrivateKey = userKey;
                    enterpriseConfig.mClientCertificate = readCertificate(in);
                    return enterpriseConfig;
                }

                private X509Certificate readCertificate(Parcel in) {
                    X509Certificate cert = null;
                    int len = in.readInt();
                    if (len > 0) {
                        try {
                            byte[] bytes = new byte[len];
                            in.readByteArray(bytes);
                            CertificateFactory cFactory = CertificateFactory.getInstance("X.509");
                            cert = (X509Certificate) cFactory
                                    .generateCertificate(new ByteArrayInputStream(bytes));
                        } catch (CertificateException e) {
                            cert = null;
                        }
                    }
                    return cert;
                }

                public WifiEnterpriseConfig[] newArray(int size) {
                    return new WifiEnterpriseConfig[size];
                }
@@ -145,13 +233,13 @@ public class WifiEnterpriseConfig implements Parcelable {
        public static final String[] strings = {EMPTY_VALUE, "PAP", "MSCHAP", "MSCHAPV2", "GTC" };
    }

    /** Internal use only @hide */
    public HashMap<String, String> getFields() {
    /** Internal use only */
    HashMap<String, String> getFields() {
        return mFields;
    }

    /** Internal use only @hide */
    public static String[] getSupplicantKeys() {
    /** Internal use only */
    static String[] getSupplicantKeys() {
        return new String[] { EAP_KEY, PHASE2_KEY, IDENTITY_KEY, ANON_IDENTITY_KEY, PASSWORD_KEY,
                CLIENT_CERT_KEY, CA_CERT_KEY, SUBJECT_MATCH_KEY, ENGINE_KEY, ENGINE_ID_KEY,
                PRIVATE_KEY_ID_KEY };
@@ -271,19 +359,37 @@ public class WifiEnterpriseConfig implements Parcelable {
     * a certificate
     * </p>
     * @param alias identifies the certificate
     * @hide
     */
    public void setCaCertificate(String alias) {
    public void setCaCertificateAlias(String alias) {
        setFieldValue(CA_CERT_KEY, alias, CA_CERT_PREFIX);
    }

    /**
     * Get CA certificate alias
     * @return alias to the CA certificate
     * @hide
     */
    public String getCaCertificate() {
    public String getCaCertificateAlias() {
        return getFieldValue(CA_CERT_KEY, CA_CERT_PREFIX);
    }

    /**
     * Specify a X.509 certificate that identifies the server.
     *
     * <p>A default name is automatically assigned to the certificate and used
     * with this configuration.
     * @param cert X.509 CA certificate
     * @throws IllegalArgumentException if not a CA certificate
     */
    public void setCaCertificate(X509Certificate cert) {
        if (cert.getBasicConstraints() >= 0) {
            mCaCert = cert;
        } else {
            throw new IllegalArgumentException("Not a CA certificate");
        }
    }

    /**
     * Set Client certificate alias.
     *
@@ -291,8 +397,9 @@ public class WifiEnterpriseConfig implements Parcelable {
     * a certificate
     * </p>
     * @param alias identifies the certificate
     * @hide
     */
    public void setClientCertificate(String alias) {
    public void setClientCertificateAlias(String alias) {
        setFieldValue(CLIENT_CERT_KEY, alias, CLIENT_CERT_PREFIX);
        setFieldValue(PRIVATE_KEY_ID_KEY, alias, Credentials.USER_PRIVATE_KEY);
        // Also, set engine parameters
@@ -308,11 +415,117 @@ public class WifiEnterpriseConfig implements Parcelable {
    /**
     * Get client certificate alias
     * @return alias to the client certificate
     * @hide
     */
    public String getClientCertificate() {
    public String getClientCertificateAlias() {
        return getFieldValue(CLIENT_CERT_KEY, CLIENT_CERT_PREFIX);
    }

    /**
     * Specify a private key and client certificate for client authorization.
     *
     * <p>A default name is automatically assigned to the key entry and used
     * with this configuration.
     * @param privateKey
     * @param clientCertificate
     */
    public void setClientKeyEntry(PrivateKey privateKey, X509Certificate clientCertificate) {
        if (clientCertificate != null) {
            if (clientCertificate.getBasicConstraints() != -1) {
                throw new IllegalArgumentException("Cannot be a CA certificate");
            }
            if (privateKey == null) {
                throw new IllegalArgumentException("Client cert without a private key");
            }
            if (privateKey.getEncoded() == null) {
                throw new IllegalArgumentException("Private key cannot be encoded");
            }
        }

        mClientPrivateKey = privateKey;
        mClientCertificate = clientCertificate;
    }

    boolean needsKeyStore() {
        // Has no keys to be installed
        if (mClientCertificate == null && mCaCert == null) return false;
        return true;
    }

    boolean installKeys(android.security.KeyStore keyStore, String name) {
        boolean ret = true;
        String privKeyName = Credentials.USER_PRIVATE_KEY + name;
        String userCertName = Credentials.USER_CERTIFICATE + name;
        String caCertName = Credentials.CA_CERTIFICATE + name;
        if (mClientCertificate != null) {
            byte[] privKeyData = mClientPrivateKey.getEncoded();
            ret = keyStore.importKey(privKeyName, privKeyData);
            if (ret == false) {
                return ret;
            }

            ret = putCertInKeyStore(keyStore, userCertName, mClientCertificate);
            if (ret == false) {
                // Remove private key installed
                keyStore.delKey(privKeyName);
                return ret;
            }
        }

        if (mCaCert != null) {
            ret = putCertInKeyStore(keyStore, caCertName, mCaCert);
            if (ret == false) {
                if (mClientCertificate != null) {
                    // Remove client key+cert
                    keyStore.delKey(privKeyName);
                    keyStore.delete(userCertName);
                }
                return ret;
            }
        }

        // Set alias names
        if (mClientCertificate != null) {
            setClientCertificateAlias(name);
            mClientPrivateKey = null;
            mClientCertificate = null;
        }

        if (mCaCert != null) {
            setCaCertificateAlias(name);
            mCaCert = null;
        }

        return ret;
    }

    private boolean putCertInKeyStore(android.security.KeyStore keyStore, String name,
            Certificate cert) {
        try {
            byte[] certData = Credentials.convertToPem(cert);
            return keyStore.put(name, certData);
        } catch (IOException e1) {
            return false;
        } catch (CertificateException e2) {
            return false;
        }
    }

    void removeKeys(android.security.KeyStore keyStore) {
        String client = getFieldValue(CLIENT_CERT_KEY, CLIENT_CERT_PREFIX);
        // a valid client certificate is configured
        if (!TextUtils.isEmpty(client)) {
            keyStore.delKey(Credentials.USER_PRIVATE_KEY + client);
            keyStore.delete(Credentials.USER_CERTIFICATE + client);
        }

        String ca = getFieldValue(CA_CERT_KEY, CA_CERT_PREFIX);
        // a valid ca certificate is configured
        if (!TextUtils.isEmpty(ca)) {
            keyStore.delete(Credentials.CA_CERTIFICATE + ca);
        }
    }

    /**
     * Set subject match. This is the substring to be matched against the subject of the
     * authentication server certificate.
@@ -330,13 +543,28 @@ public class WifiEnterpriseConfig implements Parcelable {
        return getFieldValue(SUBJECT_MATCH_KEY, "");
    }

    /** See {@link WifiConfiguration#getKeyIdForCredentials} @hide */
    String getKeyId(WifiEnterpriseConfig current) {
        String eap = mFields.get(EAP_KEY);
        String phase2 = mFields.get(PHASE2_KEY);

        // If either eap or phase2 are not initialized, use current config details
        if (TextUtils.isEmpty((eap))) {
            eap = current.mFields.get(EAP_KEY);
        }
        if (TextUtils.isEmpty(phase2)) {
            phase2 = current.mFields.get(PHASE2_KEY);
        }
        return eap + "_" + phase2;
    }

    /** Migrates the old style TLS config to the new config style. This should only be used
     * when restoring an old wpa_supplicant.conf or upgrading from a previous
     * platform version.
     * @return true if the config was updated
     * @hide
     */
    public boolean migrateOldEapTlsNative(WifiNative wifiNative, int netId) {
    boolean migrateOldEapTlsNative(WifiNative wifiNative, int netId) {
        String oldPrivateKey = wifiNative.getNetworkVariable(netId, OLD_PRIVATE_KEY_NAME);
        /*
         * If the old configuration value is not present, then there is nothing
@@ -395,6 +623,7 @@ public class WifiEnterpriseConfig implements Parcelable {
     * @return the index into array
     */
    private int getStringIndex(String arr[], String toBeFound, int defaultIndex) {
        if (TextUtils.isEmpty(toBeFound)) return defaultIndex;
        for (int i = 0; i < arr.length; i++) {
            if (toBeFound.equals(arr[i])) return i;
        }
@@ -408,7 +637,8 @@ public class WifiEnterpriseConfig implements Parcelable {
     */
    private String getFieldValue(String key, String prefix) {
        String value = mFields.get(key);
        if (EMPTY_VALUE.equals(value)) return "";
        // Uninitialized or known to be empty after reading from supplicant
        if (TextUtils.isEmpty(value) || EMPTY_VALUE.equals(value)) return "";
        return removeDoubleQuotes(value).substring(prefix.length());
    }