OSDN Git Service

Integrate VPN with new keystore and misc fixes.
authorHung-ying Tyan <tyanh@google.com>
Mon, 6 Jul 2009 09:26:34 +0000 (17:26 +0800)
committerHung-ying Tyan <tyanh@google.com>
Mon, 6 Jul 2009 10:47:47 +0000 (18:47 +0800)
* Changes
  + Pass intent to keystore when needed and hooks to resume from it.
  + Generate random, unique ID for profile instead of base64 from its
    name.
  + Add VPN to "Wirless controls" description.
  + Add credential storage to "Security & location" description.
  + More hints to set password and unlock dialogs in credential storage
    settings for actions that come from other processes.
  + Sort VPN profiles according to the names.
  + Replace Keystore with CertTool in L2tpIpsecEditor

res/layout/cstor_unlock_dialog_view.xml
res/values/strings.xml
src/com/android/settings/SecuritySettings.java
src/com/android/settings/vpn/L2tpEditor.java
src/com/android/settings/vpn/L2tpIpsecEditor.java
src/com/android/settings/vpn/L2tpIpsecPskEditor.java
src/com/android/settings/vpn/Util.java
src/com/android/settings/vpn/VpnEditor.java
src/com/android/settings/vpn/VpnProfileEditor.java
src/com/android/settings/vpn/VpnSettings.java

index 66a2175..895306a 100644 (file)
             android:layout_height="fill_parent"
             android:padding="15dip">
 
+        <TextView android:id="@+id/cstor_access_dialog_hint_from_action"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:textSize="@dimen/vpn_connect_normal_text_size"
+                android:text="@string/cstor_access_dialog_hint_from_action"
+                android:layout_marginBottom="10sp" />
+
         <TextView android:id="@+id/cstor_error"
                 android:layout_width="fill_parent"
                 android:layout_height="wrap_content"
index 64cc500..1022a2e 100644 (file)
     <!-- Main Settings screen settings title for things like Wi-Fi, bluetooth, airplane mode.  This will take you to another screen with those settings. -->
     <string name="radio_controls_title">Wireless controls</string>
     <!-- Main Settings screen settings summary text for the "Wireless controls" setting -->
-    <string name="radio_controls_summary">Manage Wi-Fi, Bluetooth, airplane mode, &amp; mobile networks</string>
+    <string name="radio_controls_summary">Manage Wi-Fi, Bluetooth, airplane mode, mobile networks, &amp; VPNs</string>
 
 
     <!-- mobile network settings screen, setting check box title -->
     <!-- Main Settings screen setting option title for the item to take you the security and location screen -->
     <string name="security_settings_title">Security &amp; location</string>
     <!-- Main Settings screen setting option summary text for the item tot ake you to the security and location screen -->
-    <string name="security_settings_summary">Set My Location, screen unlock, SIM card lock</string>
+    <string name="security_settings_summary">Set My Location, screen unlock, SIM card lock, credential storage lock</string>
     <!-- In the security screen, the header title for settings related to  Passwords-->
     <string name="security_passwords_title">Passwords</string>
 
@@ -1946,6 +1946,8 @@ found in the list of installed applications.</string>
     <string name="cstor_access_summary">Allow applications to access secure certificates and other credentials</string>
     <!-- Title of dialog to enable/dislable access to credential storage -->
     <string name="cstor_access_dialog_title">Enter password</string>
+    <!-- Description of dialog to enable/dislable access to credential storage from an action that requires the credential storage -->
+    <string name="cstor_access_dialog_hint_from_action">This action requires enabling the credential storage. Please enter the password to enable it.</string>
 
     <!-- Title of preference to set storage password -->
     <string name="cstor_set_passwd_title">Set password</string>
@@ -1977,6 +1979,8 @@ found in the list of installed applications.</string>
     <string name="cstor_confirm_password">Confirm new password:</string>
     <!-- Description when user set up the storage for the very first time -->
     <string name="cstor_first_time_hint">You must set a password for the credential storage.</string>
+    <!-- Description when user set up the storage for the very first time from an action that requires the credential storage-->
+    <string name="cstor_first_time_hint_from_action">This action requires the credential storage but the storage has not been activated before. To activiate it, you must set a password for the credential storage.</string>
     <string name="cstor_password_error">Please enter the correct password.</string>
     <string name="cstor_password_error_reset_warning">Please enter the correct password. You have one more try to enter the correct password before the credential storage is erased.</string>
     <string name="cstor_password_error_reset_warning_plural">Please enter the correct password. You have %d more tries to enter the correct password before the credential storage is erased.</string>
index 4e77d6b..73578c7 100644 (file)
@@ -577,8 +577,7 @@ public class SecuritySettings extends PreferenceActivity implements
                     : R.string.cstor_password_error);
             if (count <= 3) {
                 if (count == 1) {
-                    v.setText(getString(
-                            R.string.cstor_password_error_reset_warning));
+                    v.setText(R.string.cstor_password_error_reset_warning);
                 } else {
                     String format = getString(
                             R.string.cstor_password_error_reset_warning_plural);
@@ -691,11 +690,15 @@ public class SecuritySettings extends PreferenceActivity implements
             return v;
         }
 
-        private void hideError() {
-            View v = mView.findViewById(R.id.cstor_error);
+        private void hide(int viewId) {
+            View v = mView.findViewById(viewId);
             if (v != null) v.setVisibility(View.GONE);
         }
 
+        private void hideError() {
+            hide(R.id.cstor_error);
+        }
+
         private String getText(int viewId) {
             return ((TextView) mView.findViewById(viewId)).getText().toString();
         }
@@ -705,6 +708,11 @@ public class SecuritySettings extends PreferenceActivity implements
             if (v != null) v.setText(text);
         }
 
+        private void setText(int viewId, int textId) {
+            TextView v = (TextView) mView.findViewById(viewId);
+            if (v != null) v.setText(textId);
+        }
+
         private void enablePreferences(boolean enabled) {
             mAccessCheckBox.setEnabled(enabled);
             mResetButton.setEnabled(enabled);
@@ -773,6 +781,12 @@ public class SecuritySettings extends PreferenceActivity implements
                     R.layout.cstor_unlock_dialog_view, null);
             hideError();
 
+            // show extra hint only when the action comes from outside
+            if ((mSpecialIntent == null)
+                    && (mCstorAddCredentialHelper == null)) {
+                hide(R.id.cstor_access_dialog_hint_from_action);
+            }
+
             Dialog d = new AlertDialog.Builder(SecuritySettings.this)
                     .setView(mView)
                     .setTitle(R.string.cstor_access_dialog_title)
@@ -790,6 +804,13 @@ public class SecuritySettings extends PreferenceActivity implements
                     R.layout.cstor_set_password_dialog_view, null);
             hideError();
 
+            // show extra hint only when the action comes from outside
+            if ((mSpecialIntent != null)
+                    || (mCstorAddCredentialHelper != null)) {
+                setText(R.id.cstor_first_time_hint,
+                        R.string.cstor_first_time_hint_from_action);
+            }
+
             switch (id) {
                 case CSTOR_INIT_DIALOG:
                     mView.findViewById(R.id.cstor_old_password_block)
@@ -835,9 +856,9 @@ public class SecuritySettings extends PreferenceActivity implements
             hideError();
 
             setText(R.id.cstor_credential_name_title,
-                    getString(R.string.cstor_credential_name));
+                    R.string.cstor_credential_name);
             setText(R.id.cstor_credential_info_title,
-                    getString(R.string.cstor_credential_info));
+                    R.string.cstor_credential_info);
             setText(R.id.cstor_credential_info,
                     mCstorAddCredentialHelper.getDescription().toString());
 
index 88a1142..c518dec 100644 (file)
@@ -60,12 +60,6 @@ class L2tpEditor extends VpnProfileEditor {
                 : validate(mSecretString, R.string.vpn_l2tp_secret));
     }
 
-    @Override
-    public void saveSecrets(String originalProfileName) {
-        L2tpProfile profile = (L2tpProfile) getProfile();
-        // TODO: fill up the implementation after keystore is available
-    }
-
     private Preference createSecretPreference(Context c) {
         final L2tpProfile profile = (L2tpProfile) getProfile();
         CheckBoxPreference secret = mSecret = new CheckBoxPreference(c);
index e79760c..b6b244f 100644 (file)
@@ -24,7 +24,7 @@ import android.preference.EditTextPreference;
 import android.preference.ListPreference;
 import android.preference.Preference;
 import android.preference.PreferenceGroup;
-import android.security.Keystore;
+import android.security.CertTool;
 import android.text.TextUtils;
 
 /**
@@ -67,7 +67,7 @@ class L2tpIpsecEditor extends L2tpEditor {
         mUserCertificate = createListPreference(c,
                 R.string.vpn_user_certificate_title,
                 mProfile.getUserCertificate(),
-                Keystore.getInstance().getAllUserCertificateKeys(),
+                CertTool.getInstance().getAllUserCertificateKeys(),
                 new Preference.OnPreferenceChangeListener() {
                     public boolean onPreferenceChange(
                             Preference pref, Object newValue) {
@@ -86,7 +86,7 @@ class L2tpIpsecEditor extends L2tpEditor {
         mCaCertificate = createListPreference(c,
                 R.string.vpn_ca_certificate_title,
                 mProfile.getCaCertificate(),
-                Keystore.getInstance().getAllCaCertificateKeys(),
+                CertTool.getInstance().getAllCaCertificateKeys(),
                 new Preference.OnPreferenceChangeListener() {
                     public boolean onPreferenceChange(
                             Preference pref, Object newValue) {
index 9c1d02c..fb67c98 100644 (file)
@@ -50,13 +50,6 @@ class L2tpIpsecPskEditor extends L2tpEditor {
                 : validate(mPresharedKey, R.string.vpn_ipsec_presharedkey));
     }
 
-    @Override
-    public void saveSecrets(String originalProfileName) {
-        L2tpIpsecPskProfile profile = (L2tpIpsecPskProfile) getProfile();
-        profile.getPresharedKey();
-        // TODO: fill up the implementation after keystore is available
-    }
-
     private Preference createPresharedKeyPreference(Context c) {
         final L2tpIpsecPskProfile profile = (L2tpIpsecPskProfile) getProfile();
         mPresharedKey = createSecretPreference(c,
index e4316fd..a37049d 100644 (file)
@@ -23,8 +23,6 @@ import android.content.Context;
 import android.content.DialogInterface;
 import android.widget.Toast;
 
-import org.apache.commons.codec.binary.Base64;
-
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
@@ -59,10 +57,6 @@ class Util {
         createErrorDialog(c, message, listener).show();
     }
 
-    static String base64Encode(byte[] bytes) {
-        return new String(Base64.encodeBase64(bytes));
-    }
-
     static void deleteFile(String path) {
         deleteFile(new File(path));
     }
index e33ce55..1d419ea 100644 (file)
@@ -46,7 +46,6 @@ public class VpnEditor extends PreferenceActivity {
 
     private VpnProfileEditor mProfileEditor;
     private boolean mAddingProfile;
-    private String mOriginalProfileName;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -54,9 +53,6 @@ public class VpnEditor extends PreferenceActivity {
         VpnProfile p = (VpnProfile) ((savedInstanceState == null)
                 ? getIntent().getParcelableExtra(VpnSettings.KEY_VPN_PROFILE)
                 : savedInstanceState.getParcelable(KEY_PROFILE));
-        mOriginalProfileName = (savedInstanceState == null)
-                ? p.getName()
-                : savedInstanceState.getString(KEY_ORIGINAL_PROFILE_NAME);
         mProfileEditor = getEditor(p);
         mAddingProfile = TextUtils.isEmpty(p.getName());
 
@@ -71,7 +67,6 @@ public class VpnEditor extends PreferenceActivity {
         if (mProfileEditor == null) return;
 
         outState.putParcelable(KEY_PROFILE, getProfile());
-        outState.putString(KEY_ORIGINAL_PROFILE_NAME, mOriginalProfileName);
     }
 
     @Override
@@ -126,13 +121,11 @@ public class VpnEditor extends PreferenceActivity {
             return false;
         }
 
-        mProfileEditor.saveSecrets(mOriginalProfileName);
         setResult(getProfile());
         return true;
     }
 
     private void setResult(VpnProfile p) {
-        p.setId(Util.base64Encode(p.getName().getBytes()));
         Intent intent = new Intent(this, VpnSettings.class);
         intent.putExtra(VpnSettings.KEY_VPN_PROFILE, (Parcelable) p);
         setResult(RESULT_OK, intent);
index b679bcb..a708a8c 100644 (file)
@@ -92,13 +92,6 @@ class VpnProfileEditor {
     }
 
     /**
-     * Saves the secrets in this profile.
-     * @param originalProfileName the original profile name
-     */
-    public void saveSecrets(String originalProfileName) {
-    }
-
-    /**
      * Creates a preference for users to input domain suffices.
      */
     protected EditTextPreference createDomainSufficesPreference(Context c) {
index bbbe9b9..e429f9f 100644 (file)
@@ -17,6 +17,7 @@
 package com.android.settings.vpn;
 
 import com.android.settings.R;
+import com.android.settings.SecuritySettings;
 
 import android.app.AlertDialog;
 import android.app.Dialog;
@@ -24,6 +25,8 @@ import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.net.vpn.L2tpIpsecPskProfile;
+import android.net.vpn.L2tpProfile;
 import android.net.vpn.VpnManager;
 import android.net.vpn.VpnProfile;
 import android.net.vpn.VpnState;
@@ -38,6 +41,7 @@ import android.preference.PreferenceCategory;
 import android.preference.PreferenceManager;
 import android.preference.PreferenceScreen;
 import android.preference.Preference.OnPreferenceClickListener;
+import android.security.Keystore;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.ContextMenu;
@@ -54,8 +58,8 @@ import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
@@ -113,6 +117,9 @@ public class VpnSettings extends PreferenceActivity implements
     private VpnProfileActor mConnectingActor;
     private boolean mStateSaved = false;
 
+    // states saved for unlocking keystore
+    private Runnable mUnlockAction;
+
     private VpnManager mVpnManager = new VpnManager(this);
 
     private ConnectivityReceiver mConnectivityReceiver =
@@ -169,6 +176,12 @@ public class VpnSettings extends PreferenceActivity implements
     public void onResume() {
         super.onResume();
         mStatusChecker.onResume();
+
+        if ((mUnlockAction != null) && isKeystoreUnlocked()) {
+            Runnable action = mUnlockAction;
+            mUnlockAction = null;
+            runOnUiThread(action);
+        }
     }
 
     @Override
@@ -279,9 +292,9 @@ public class VpnSettings extends PreferenceActivity implements
     }
 
     @Override
-    protected void onActivityResult(int requestCode, int resultCode,
-            Intent data) {
-        int index = mIndexOfEditedProfile;
+    protected void onActivityResult(final int requestCode, final int resultCode,
+            final Intent data) {
+        final int index = mIndexOfEditedProfile;
         mIndexOfEditedProfile = -1;
 
         if ((resultCode == RESULT_CANCELED) || (data == null)) {
@@ -312,6 +325,16 @@ public class VpnSettings extends PreferenceActivity implements
                 return;
             }
 
+            if (needKeystoreToSave(p)) {
+                Runnable action = new Runnable() {
+                    public void run() {
+                        mIndexOfEditedProfile = index;
+                        onActivityResult(requestCode, resultCode, data);
+                    }
+                };
+                if (!unlockKeystore(p, action)) return;
+            }
+
             try {
                 if ((index < 0) || (index >= mVpnProfileList.size())) {
                     addProfile(p);
@@ -414,7 +437,27 @@ public class VpnSettings extends PreferenceActivity implements
                 .show();
     }
 
+    // Randomly generates an ID for the profile.
+    // The ID is unique and only set once when the profile is created.
+    private void setProfileId(VpnProfile profile) {
+        String id;
+
+        while (true) {
+            id = String.valueOf(Math.abs(
+                    Double.doubleToLongBits(Math.random())));
+            if (id.length() >= 8) break;
+        }
+        for (VpnProfile p : mVpnProfileList) {
+            if (p.getId().equals(id)) {
+                setProfileId(profile);
+                return;
+            }
+        }
+        profile.setId(id);
+    }
+
     private void addProfile(VpnProfile p) throws IOException {
+        setProfileId(p);
         saveProfileToStorage(p);
         mVpnProfileList.add(p);
         addPreferenceFor(p);
@@ -445,8 +488,11 @@ public class VpnSettings extends PreferenceActivity implements
             throw new RuntimeException("inconsistent state!");
         }
 
-        // TODO: call saveSecret(String) after keystore is available
+        p.setId(oldProfile.getId());
+
+        processSecrets(p);
 
+        // TODO: remove copyFiles once the setId() code propagates.
         // Copy config files and remove the old ones if they are in different
         // directories.
         if (Util.copyFiles(getProfileDir(oldProfile), getProfileDir(p))) {
@@ -463,25 +509,93 @@ public class VpnSettings extends PreferenceActivity implements
         startActivityForResult(intent, REQUEST_SELECT_VPN_TYPE);
     }
 
-    private void startVpnEditor(VpnProfile profile) {
+    private boolean isKeystoreUnlocked() {
+        return (Keystore.getInstance().getState() == Keystore.UNLOCKED);
+    }
+
+
+    // Returns true if the profile needs to access keystore
+    private boolean needKeystoreToSave(VpnProfile p) {
+        return needKeystoreToConnect(p);
+    }
+
+    // Returns true if the profile needs to access keystore
+    private boolean needKeystoreToEdit(VpnProfile p) {
+        switch (p.getType()) {
+            case L2TP_IPSEC:
+            case L2TP_IPSEC_PSK:
+                return true;
+
+            default:
+                return false;
+        }
+    }
+
+    // Returns true if the profile needs to access keystore
+    private boolean needKeystoreToConnect(VpnProfile p) {
+        switch (p.getType()) {
+            case L2TP_IPSEC:
+            case L2TP_IPSEC_PSK:
+                return true;
+
+            case L2TP:
+                return ((L2tpProfile) p).isSecretEnabled();
+
+            default:
+                return false;
+        }
+    }
+
+    // Returns true if keystore is unlocked or keystore is not a concern
+    private boolean unlockKeystore(VpnProfile p, Runnable action) {
+        if (isKeystoreUnlocked()) return true;
+        mUnlockAction = action;
+        startActivity(
+                new Intent(SecuritySettings.ACTION_UNLOCK_CREDENTIAL_STORAGE));
+        return false;
+    }
+
+    private void startVpnEditor(final VpnProfile profile) {
+        if (needKeystoreToEdit(profile)) {
+            Runnable action = new Runnable() {
+                public void run() {
+                    startVpnEditor(profile);
+                }
+            };
+            if (!unlockKeystore(profile, action)) return;
+        }
+
         Intent intent = new Intent(this, VpnEditor.class);
         intent.putExtra(KEY_VPN_PROFILE, (Parcelable) profile);
         startActivityForResult(intent, REQUEST_ADD_OR_EDIT_PROFILE);
     }
 
+    private synchronized void connect(final VpnProfile p) {
+        if (needKeystoreToConnect(p)) {
+            Runnable action = new Runnable() {
+                public void run() {
+                    connect(p);
+                }
+            };
+            if (!unlockKeystore(p, action)) return;
+        }
+
+        mConnectingActor = getActor(p);
+        if (mConnectingActor.isConnectDialogNeeded()) {
+            removeDialog(DIALOG_CONNECT);
+            showDialog(DIALOG_CONNECT);
+        } else {
+            changeState(p, VpnState.CONNECTING);
+            mConnectingActor.connect(null);
+        }
+    }
+
     // Do connect or disconnect based on the current state.
     private synchronized void connectOrDisconnect(VpnProfile p) {
         VpnPreference pref = mVpnPreferenceMap.get(p.getName());
         switch (p.getState()) {
             case IDLE:
-                mConnectingActor = getActor(p);
-                if (mConnectingActor.isConnectDialogNeeded()) {
-                    removeDialog(DIALOG_CONNECT);
-                    showDialog(DIALOG_CONNECT);
-                } else {
-                    changeState(p, VpnState.CONNECTING);
-                    mConnectingActor.connect(null);
-                }
+                connect(p);
                 break;
 
             case CONNECTING:
@@ -601,7 +715,6 @@ public class VpnSettings extends PreferenceActivity implements
         File root = new File(PROFILES_ROOT);
         String[] dirs = root.list();
         if (dirs == null) return;
-        Arrays.sort(dirs);
         for (String dir : dirs) {
             File f = new File(new File(root, dir), PROFILE_OBJ_FILE);
             if (!f.exists()) continue;
@@ -611,11 +724,21 @@ public class VpnSettings extends PreferenceActivity implements
                 if (!checkIdConsistency(dir, p)) continue;
 
                 mVpnProfileList.add(p);
-                addPreferenceFor(p);
             } catch (IOException e) {
                 Log.e(TAG, "retrieveVpnListFromStorage()", e);
             }
         }
+        Collections.sort(mVpnProfileList, new Comparator<VpnProfile>() {
+            public int compare(VpnProfile p1, VpnProfile p2) {
+                return p1.getName().compareTo(p2.getName());
+            }
+
+            public boolean equals(VpnProfile p) {
+                // not used
+                return false;
+            }
+        });
+        for (VpnProfile p : mVpnProfileList) addPreferenceFor(p);
         disableProfilePreferencesIfOneActive();
     }
 
@@ -668,6 +791,40 @@ public class VpnSettings extends PreferenceActivity implements
         return mVpnManager.createVpnProfile(Enum.valueOf(VpnType.class, type));
     }
 
+    private static final String NAMESPACE_VPN = "vpn";
+    private static final String KEY_PREFIX_IPSEC_PSK = "ipsk000";
+    private static final String KEY_PREFIX_L2TP_SECRET = "lscrt000";
+
+    private void processSecrets(VpnProfile p) {
+        Keystore ks = Keystore.getInstance();
+        switch (p.getType()) {
+            case L2TP_IPSEC_PSK:
+                L2tpIpsecPskProfile pskProfile = (L2tpIpsecPskProfile) p;
+                String keyName = KEY_PREFIX_IPSEC_PSK + p.getId();
+                String presharedKey = pskProfile.getPresharedKey();
+                if (!presharedKey.equals(keyName)) {
+                    ks.put(NAMESPACE_VPN, keyName, presharedKey);
+                    pskProfile.setPresharedKey(NAMESPACE_VPN + "_" + keyName);
+                }
+                // pass through
+
+            case L2TP:
+                L2tpProfile l2tpProfile = (L2tpProfile) p;
+                keyName = KEY_PREFIX_L2TP_SECRET + p.getId();
+                String secret = l2tpProfile.getSecretString();
+                if (l2tpProfile.isSecretEnabled()) {
+                    if (!secret.equals(keyName)) {
+                        ks.put(NAMESPACE_VPN, keyName, secret);
+                        l2tpProfile.setSecretString(
+                                NAMESPACE_VPN + "_" + keyName);
+                    }
+                } else {
+                    ks.remove(NAMESPACE_VPN, keyName);
+                }
+                break;
+        }
+    }
+
     private class VpnPreference extends Preference {
         VpnProfile mProfile;
         VpnPreference(Context c, VpnProfile p) {