diff --git a/res/layout/cstor_unlock_dialog_view.xml b/res/layout/cstor_unlock_dialog_view.xml index 66a21754d4a..895306a39e7 100644 --- a/res/layout/cstor_unlock_dialog_view.xml +++ b/res/layout/cstor_unlock_dialog_view.xml @@ -24,6 +24,13 @@ android:layout_height="fill_parent" android:padding="15dip"> + + Wireless controls - Manage Wi-Fi, Bluetooth, airplane mode, & mobile networks + Manage Wi-Fi, Bluetooth, airplane mode, mobile networks, & VPNs @@ -488,7 +488,7 @@ Security & location - Set My Location, screen unlock, SIM card lock + Set My Location, screen unlock, SIM card lock, credential storage lock Passwords @@ -1946,6 +1946,8 @@ found in the list of installed applications. Allow applications to access secure certificates and other credentials Enter password + + This action requires enabling the credential storage. Please enter the password to enable it. Set password @@ -1977,6 +1979,8 @@ found in the list of installed applications. Confirm new password: You must set a password for the credential storage. + + 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. Please enter the correct password. Please enter the correct password. You have one more try to enter the correct password before the credential storage is erased. Please enter the correct password. You have %d more tries to enter the correct password before the credential storage is erased. diff --git a/src/com/android/settings/SecuritySettings.java b/src/com/android/settings/SecuritySettings.java index 4e77d6b3c14..73578c738ac 100644 --- a/src/com/android/settings/SecuritySettings.java +++ b/src/com/android/settings/SecuritySettings.java @@ -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()); diff --git a/src/com/android/settings/vpn/L2tpEditor.java b/src/com/android/settings/vpn/L2tpEditor.java index 88a1142a156..c518dec46a6 100644 --- a/src/com/android/settings/vpn/L2tpEditor.java +++ b/src/com/android/settings/vpn/L2tpEditor.java @@ -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); diff --git a/src/com/android/settings/vpn/L2tpIpsecEditor.java b/src/com/android/settings/vpn/L2tpIpsecEditor.java index e79760c8dd8..b6b244f7ee5 100644 --- a/src/com/android/settings/vpn/L2tpIpsecEditor.java +++ b/src/com/android/settings/vpn/L2tpIpsecEditor.java @@ -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) { diff --git a/src/com/android/settings/vpn/L2tpIpsecPskEditor.java b/src/com/android/settings/vpn/L2tpIpsecPskEditor.java index 9c1d02c3903..fb67c987ffc 100644 --- a/src/com/android/settings/vpn/L2tpIpsecPskEditor.java +++ b/src/com/android/settings/vpn/L2tpIpsecPskEditor.java @@ -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, diff --git a/src/com/android/settings/vpn/Util.java b/src/com/android/settings/vpn/Util.java index e4316fd0831..a37049d2ca2 100644 --- a/src/com/android/settings/vpn/Util.java +++ b/src/com/android/settings/vpn/Util.java @@ -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)); } diff --git a/src/com/android/settings/vpn/VpnEditor.java b/src/com/android/settings/vpn/VpnEditor.java index e33ce550904..1d419eab215 100644 --- a/src/com/android/settings/vpn/VpnEditor.java +++ b/src/com/android/settings/vpn/VpnEditor.java @@ -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); diff --git a/src/com/android/settings/vpn/VpnProfileEditor.java b/src/com/android/settings/vpn/VpnProfileEditor.java index b679bcb6550..a708a8c89d5 100644 --- a/src/com/android/settings/vpn/VpnProfileEditor.java +++ b/src/com/android/settings/vpn/VpnProfileEditor.java @@ -91,13 +91,6 @@ class VpnProfileEditor { : validate(mServerName, R.string.vpn_vpn_server)); } - /** - * 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. */ diff --git a/src/com/android/settings/vpn/VpnSettings.java b/src/com/android/settings/vpn/VpnSettings.java index bbbe9b92a5b..e429f9f37e5 100644 --- a/src/com/android/settings/vpn/VpnSettings.java +++ b/src/com/android/settings/vpn/VpnSettings.java @@ -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() { + 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) {