OSDN Git Service

Fix EAP-TLS reconnect after reboot issue
authorVinit Deshapnde <vinitd@google.com>
Thu, 3 Oct 2013 00:26:05 +0000 (17:26 -0700)
committerVinit Deshapnde <vinitd@google.com>
Thu, 3 Oct 2013 00:26:05 +0000 (17:26 -0700)
After a reboot, KeyStore is locked, and certificates encrypted with user
PIN are not accessible. So statemachines are not able to connect to
EAP-TLS networks. This change makes the problem less severe by

1. Not signing certificates with user PIN on devices with hardware backed
KeyStore.
2. Issuing a reconnect upon first USER_PRESENT event.

This means HH (which has a hardware backed keystore) can connect to
EAP-TLS networks without requiring user intervention and other devices
will automatically connect to those networks after user punches PIN.

Bug: 10325089

Change-Id: I023d60e58d8214152f051bd9ec84b85b702d829a

services/java/com/android/server/wifi/WifiController.java
services/java/com/android/server/wifi/WifiService.java
wifi/java/android/net/wifi/WifiConfigStore.java
wifi/java/android/net/wifi/WifiEnterpriseConfig.java
wifi/java/android/net/wifi/WifiStateMachine.java

index 87b4394..a3d514e 100644 (file)
@@ -57,6 +57,7 @@ class WifiController extends StateMachine {
     private int mStayAwakeConditions;
     private long mIdleMillis;
     private int mSleepPolicy;
+    private boolean mFirstUserSignOnSeen = false;
 
     private AlarmManager mAlarmManager;
     private PendingIntent mIdleIntent;
@@ -113,6 +114,7 @@ class WifiController extends StateMachine {
     static final int CMD_AIRPLANE_TOGGLED           = BASE + 9;
     static final int CMD_SET_AP                     = BASE + 10;
     static final int CMD_DEFERRED_TOGGLE            = BASE + 11;
+    static final int CMD_USER_PRESENT               = BASE + 12;
 
     private DefaultState mDefaultState = new DefaultState();
     private StaEnabledState mStaEnabledState = new StaEnabledState();
@@ -361,6 +363,9 @@ class WifiController extends StateMachine {
                 case CMD_AIRPLANE_TOGGLED:
                 case CMD_EMERGENCY_MODE_CHANGED:
                     break;
+                case CMD_USER_PRESENT:
+                    mFirstUserSignOnSeen = true;
+                    break;
                 case CMD_DEFERRED_TOGGLE:
                     log("DEFERRED_TOGGLE ignored due to state change");
                     break;
@@ -639,6 +644,15 @@ class WifiController extends StateMachine {
             if (msg.what == CMD_DEVICE_IDLE) {
                 checkLocksAndTransitionWhenDeviceIdle();
                 // We let default state handle the rest of work
+            } else if (msg.what == CMD_USER_PRESENT) {
+                // TLS networks can't connect until user unlocks keystore. KeyStore
+                // unlocks when the user punches PIN after the reboot. So use this
+                // trigger to get those networks connected.
+                if (mFirstUserSignOnSeen == false) {
+                    mWifiStateMachine.reloadTlsNetworksAndReconnect();
+                }
+                mFirstUserSignOnSeen = true;
+                return HANDLED;
             }
             return NOT_HANDLED;
         }
index f93a45b..86c68f3 100644 (file)
@@ -83,6 +83,7 @@ import static com.android.server.wifi.WifiController.CMD_SCAN_ALWAYS_MODE_CHANGE
 import static com.android.server.wifi.WifiController.CMD_SCREEN_OFF;
 import static com.android.server.wifi.WifiController.CMD_SCREEN_ON;
 import static com.android.server.wifi.WifiController.CMD_SET_AP;
+import static com.android.server.wifi.WifiController.CMD_USER_PRESENT;
 import static com.android.server.wifi.WifiController.CMD_WIFI_TOGGLED;
 /**
  * WifiService handles remote WiFi operation requests by implementing
@@ -1084,6 +1085,8 @@ public final class WifiService extends IWifiManager.Stub {
             String action = intent.getAction();
             if (action.equals(Intent.ACTION_SCREEN_ON)) {
                 mWifiController.sendMessage(CMD_SCREEN_ON);
+            } else if (action.equals(Intent.ACTION_USER_PRESENT)) {
+                mWifiController.sendMessage(CMD_USER_PRESENT);
             } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
                 mWifiController.sendMessage(CMD_SCREEN_OFF);
             } else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
@@ -1120,6 +1123,7 @@ public final class WifiService extends IWifiManager.Stub {
     private void registerForBroadcasts() {
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(Intent.ACTION_SCREEN_ON);
+        intentFilter.addAction(Intent.ACTION_USER_PRESENT);
         intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
         intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
         intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
index f79a4a6..a6ae215 100644 (file)
@@ -57,6 +57,7 @@ import java.io.IOException;
 import java.io.PrintWriter;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
+import java.security.PublicKey;
 import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.Collection;
@@ -742,6 +743,26 @@ class WifiConfigStore {
         markAllNetworksDisabledExcept(INVALID_NETWORK_ID);
     }
 
+    boolean needsUnlockedKeyStore() {
+
+        // Any network using certificates to authenticate access requires
+        // unlocked key store; unless the certificates can be stored with
+        // hardware encryption
+
+        for(WifiConfiguration config : mConfiguredNetworks.values()) {
+
+            if (config.allowedKeyManagement.get(KeyMgmt.WPA_EAP)
+                    && config.allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
+
+                if (config.enterpriseConfig.needsSoftwareBackedKeyStore()) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
     private void writeIpAndProxyConfigurations() {
 
         /* Make a copy */
@@ -1223,7 +1244,6 @@ class WifiConfigStore {
                      * 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) {
@@ -1583,6 +1603,7 @@ class WifiConfigStore {
         }
 
         config.enterpriseConfig.migrateCerts(mKeyStore);
+        config.enterpriseConfig.initializeSoftwareKeystoreFlag(mKeyStore);
     }
 
     private String removeDoubleQuotes(String string) {
index e357804..c7ebecb 100644 (file)
@@ -19,8 +19,10 @@ import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.Process;
 import android.security.Credentials;
+import android.security.KeyChain;
 import android.security.KeyStore;
 import android.text.TextUtils;
+import android.util.Slog;
 
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
@@ -43,6 +45,7 @@ import java.util.Map;
  */
 public class WifiEnterpriseConfig implements Parcelable {
     private static final String TAG = "WifiEnterpriseConfig";
+    private static final boolean DBG = false;
     /**
      * In old configurations, the "private_key" field was used. However, newer
      * configurations use the key_id field with the engine_id set to "keystore".
@@ -91,6 +94,7 @@ public class WifiEnterpriseConfig implements Parcelable {
     private X509Certificate mCaCert;
     private PrivateKey mClientPrivateKey;
     private X509Certificate mClientCertificate;
+    private boolean mNeedsSoftwareKeystore = false;
 
     /** This represents an empty value of an enterprise field.
      * NULL is used at wpa_supplicant to indicate an empty value
@@ -509,6 +513,18 @@ public class WifiEnterpriseConfig implements Parcelable {
         return true;
     }
 
+    static boolean isHardwareBackedKey(PrivateKey key) {
+        return KeyChain.isBoundKeyAlgorithm(key.getAlgorithm());
+    }
+
+    static boolean hasHardwareBackedKey(Certificate certificate) {
+        return KeyChain.isBoundKeyAlgorithm(certificate.getPublicKey().getAlgorithm());
+    }
+
+    boolean needsSoftwareBackedKeyStore() {
+        return mNeedsSoftwareKeystore;
+    }
+
     boolean installKeys(android.security.KeyStore keyStore, String name) {
         boolean ret = true;
         String privKeyName = Credentials.USER_PRIVATE_KEY + name;
@@ -516,8 +532,23 @@ public class WifiEnterpriseConfig implements Parcelable {
         String caCertName = Credentials.CA_CERTIFICATE + name;
         if (mClientCertificate != null) {
             byte[] privKeyData = mClientPrivateKey.getEncoded();
-            ret = keyStore.importKey(privKeyName, privKeyData, Process.WIFI_UID,
-                            KeyStore.FLAG_ENCRYPTED);
+            if (isHardwareBackedKey(mClientPrivateKey)) {
+                // Hardware backed key store is secure enough to store keys un-encrypted, this
+                // removes the need for user to punch a PIN to get access to these keys
+                if (DBG) Slog.d(TAG, "importing keys " + name + " in hardware backed " +
+                        "store");
+                ret = keyStore.importKey(privKeyName, privKeyData, Process.WIFI_UID,
+                                KeyStore.FLAG_NONE);
+            } else {
+                // Software backed key store is NOT secure enough to store keys un-encrypted.
+                // Save keys encrypted so they are protected with user's PIN. User will
+                // have to unlock phone before being able to use these keys and connect to
+                // networks.
+                if (DBG) Slog.d(TAG, "importing keys " + name + " in software backed store");
+                ret = keyStore.importKey(privKeyName, privKeyData, Process.WIFI_UID,
+                        KeyStore.FLAG_ENCRYPTED);
+                mNeedsSoftwareKeystore = true;
+            }
             if (ret == false) {
                 return ret;
             }
@@ -561,7 +592,9 @@ public class WifiEnterpriseConfig implements Parcelable {
             Certificate cert) {
         try {
             byte[] certData = Credentials.convertToPem(cert);
-            return keyStore.put(name, certData, Process.WIFI_UID, KeyStore.FLAG_ENCRYPTED);
+            if (DBG) Slog.d(TAG, "putting certificate " + name + " in keystore");
+            return keyStore.put(name, certData, Process.WIFI_UID, KeyStore.FLAG_NONE);
+
         } catch (IOException e1) {
             return false;
         } catch (CertificateException e2) {
@@ -573,6 +606,7 @@ public class WifiEnterpriseConfig implements Parcelable {
         String client = getFieldValue(CLIENT_CERT_KEY, CLIENT_CERT_PREFIX);
         // a valid client certificate is configured
         if (!TextUtils.isEmpty(client)) {
+            if (DBG) Slog.d(TAG, "removing client private key and user cert");
             keyStore.delKey(Credentials.USER_PRIVATE_KEY + client, Process.WIFI_UID);
             keyStore.delete(Credentials.USER_CERTIFICATE + client, Process.WIFI_UID);
         }
@@ -580,6 +614,7 @@ public class WifiEnterpriseConfig implements Parcelable {
         String ca = getFieldValue(CA_CERT_KEY, CA_CERT_PREFIX);
         // a valid ca certificate is configured
         if (!TextUtils.isEmpty(ca)) {
+            if (DBG) Slog.d(TAG, "removing CA cert");
             keyStore.delete(Credentials.CA_CERTIFICATE + ca, Process.WIFI_UID);
         }
     }
@@ -684,6 +719,61 @@ public class WifiEnterpriseConfig implements Parcelable {
         }
     }
 
+    void initializeSoftwareKeystoreFlag(android.security.KeyStore keyStore) {
+        String client = getFieldValue(CLIENT_CERT_KEY, CLIENT_CERT_PREFIX);
+        if (!TextUtils.isEmpty(client)) {
+            // a valid client certificate is configured
+
+            // BUGBUG: keyStore.get() never returns certBytes; because it is not
+            // taking WIFI_UID as a parameter. It always looks for certificate
+            // with SYSTEM_UID, and never finds any Wifi certificates. Assuming that
+            // all certificates need software keystore until we get the get() API
+            // fixed.
+
+            mNeedsSoftwareKeystore = true;
+
+            /*
+            try {
+
+                if (DBG) Slog.d(TAG, "Loading client certificate " + Credentials
+                        .USER_CERTIFICATE + client);
+
+                CertificateFactory factory = CertificateFactory.getInstance("X.509");
+                if (factory == null) {
+                    Slog.e(TAG, "Error getting certificate factory");
+                    return;
+                }
+
+                byte[] certBytes = keyStore.get(Credentials.USER_CERTIFICATE + client);
+                if (certBytes != null) {
+                    Certificate cert = (X509Certificate) factory.generateCertificate(
+                            new ByteArrayInputStream(certBytes));
+
+                    if (cert != null) {
+                        mNeedsSoftwareKeystore = hasHardwareBackedKey(cert);
+
+                        if (DBG) Slog.d(TAG, "Loaded client certificate " + Credentials
+                                .USER_CERTIFICATE + client);
+                        if (DBG) Slog.d(TAG, "It " + (mNeedsSoftwareKeystore ? "needs" :
+                                "does not need" ) + " software key store");
+                    } else {
+                        Slog.d(TAG, "could not generate certificate");
+                    }
+                } else {
+                    Slog.e(TAG, "Could not load client certificate " + Credentials
+                            .USER_CERTIFICATE + client);
+                    mNeedsSoftwareKeystore = true;
+                }
+
+            } catch(CertificateException e) {
+                Slog.e(TAG, "Could not read certificates");
+                mCaCert = null;
+                mClientCertificate = null;
+            }
+            */
+        }
+    }
+
     private String removeDoubleQuotes(String string) {
         if (TextUtils.isEmpty(string)) return "";
         int length = string.length();
index 76bd636..349fe24 100644 (file)
@@ -426,6 +426,8 @@ public class WifiStateMachine extends StateMachine {
     static final int CMD_IP_ADDRESS_UPDATED               = BASE + 140;
     /* An IP address was removed from our interface */
     static final int CMD_IP_ADDRESS_REMOVED               = BASE + 141;
+    /* Reload all networks and reconnect */
+    static final int CMD_RELOAD_TLS_AND_RECONNECT         = BASE + 142;
 
     /* Wifi state machine modes of operation */
     /* CONNECT_MODE - connect to any 'known' AP when it becomes available */
@@ -1320,6 +1322,14 @@ public class WifiStateMachine extends StateMachine {
     }
 
     /**
+     * Reload networks and then reconnect; helps load correct data for TLS networks
+     */
+
+    public void reloadTlsNetworksAndReconnect() {
+        sendMessage(CMD_RELOAD_TLS_AND_RECONNECT);
+    }
+
+    /**
      * Add a network synchronously
      *
      * @return network id of the new network
@@ -2455,6 +2465,7 @@ public class WifiStateMachine extends StateMachine {
                 case CMD_DISCONNECT:
                 case CMD_RECONNECT:
                 case CMD_REASSOCIATE:
+                case CMD_RELOAD_TLS_AND_RECONNECT:
                 case WifiMonitor.SUP_CONNECTION_EVENT:
                 case WifiMonitor.SUP_DISCONNECTION_EVENT:
                 case WifiMonitor.NETWORK_CONNECTION_EVENT:
@@ -3405,6 +3416,13 @@ public class WifiStateMachine extends StateMachine {
                 case CMD_REASSOCIATE:
                     mWifiNative.reassociate();
                     break;
+                case CMD_RELOAD_TLS_AND_RECONNECT:
+                    if (mWifiConfigStore.needsUnlockedKeyStore()) {
+                        logd("Reconnecting to give a chance to un-connected TLS networks");
+                        mWifiNative.disconnect();
+                        mWifiNative.reconnect();
+                    }
+                    break;
                 case WifiManager.CONNECT_NETWORK:
                     /* The connect message can contain a network id passed as arg1 on message or
                      * or a config passed as obj on message.