summaryrefslogtreecommitdiffstats
path: root/src/com/android/settings/CredentialStorage.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/settings/CredentialStorage.java')
-rw-r--r--src/com/android/settings/CredentialStorage.java492
1 files changed, 348 insertions, 144 deletions
diff --git a/src/com/android/settings/CredentialStorage.java b/src/com/android/settings/CredentialStorage.java
index 9d5a603..e246fce 100644
--- a/src/com/android/settings/CredentialStorage.java
+++ b/src/com/android/settings/CredentialStorage.java
@@ -18,213 +18,417 @@ package com.android.settings;
import android.app.Activity;
import android.app.AlertDialog;
+import android.app.admin.DevicePolicyManager;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.res.Resources;
+import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.RemoteException;
+import android.security.KeyChain.KeyChainConnection;
+import android.security.KeyChain;
import android.security.KeyStore;
import android.text.Editable;
+import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
+import com.android.internal.widget.LockPatternUtils;
-import java.io.UnsupportedEncodingException;
+/**
+ * CredentialStorage handles KeyStore reset, unlock, and install.
+ *
+ * CredentialStorage has a pretty convoluted state machine to migrate
+ * from the old style separate keystore password to a new key guard
+ * based password, as well as to deal with setting up the key guard if
+ * necessary.
+ *
+ * KeyStore: UNINITALIZED
+ * KeyGuard: OFF
+ * Action: set up key guard
+ * Notes: factory state
+ *
+ * KeyStore: UNINITALIZED
+ * KeyGuard: ON
+ * Action: confirm key guard
+ * Notes: user had key guard but no keystore and upgraded from pre-ICS
+ * OR user had key guard and pre-ICS keystore password which was then reset
+ *
+ * KeyStore: LOCKED
+ * KeyGuard: OFF/ON
+ * Action: old unlock dialog
+ * Notes: assume old password, need to use it to unlock.
+ * if unlock, ensure key guard before install.
+ * if reset, treat as UNINITALIZED/OFF
+ *
+ * KeyStore: UNLOCKED
+ * KeyGuard: OFF
+ * Action: set up key guard
+ * Notes: ensure key guard, then proceed
+ *
+ * KeyStore: UNLOCKED
+ * keyguard: ON
+ * Action: normal unlock/install
+ * Notes: this is the common case
+ */
+public final class CredentialStorage extends Activity {
-public class CredentialStorage extends Activity implements TextWatcher,
- DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
+ private static final String TAG = "CredentialStorage";
public static final String ACTION_UNLOCK = "com.android.credentials.UNLOCK";
- public static final String ACTION_SET_PASSWORD = "com.android.credentials.SET_PASSWORD";
public static final String ACTION_INSTALL = "com.android.credentials.INSTALL";
public static final String ACTION_RESET = "com.android.credentials.RESET";
- private static final String TAG = "CredentialStorage";
+ // This is the minimum acceptable password quality. If the current password quality is
+ // lower than this, keystore should not be activated.
+ static final int MIN_PASSWORD_QUALITY = DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
- private KeyStore mKeyStore = KeyStore.getInstance();
- private boolean mSubmit = false;
- private Bundle mBundle;
+ private static final int CONFIRM_KEY_GUARD_REQUEST = 1;
- private TextView mOldPassword;
- private TextView mNewPassword;
- private TextView mConfirmPassword;
- private TextView mError;
- private Button mButton;
+ private final KeyStore mKeyStore = KeyStore.getInstance();
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
+ /**
+ * When non-null, the bundle containing credentials to install.
+ */
+ private Bundle mInstallBundle;
+
+ /**
+ * After unsuccessful KeyStore.unlock, the number of unlock
+ * attempts remaining before the KeyStore will reset itself.
+ *
+ * Reset to -1 on successful unlock or reset.
+ */
+ private int mRetriesRemaining = -1;
+
+ @Override protected void onResume() {
+ super.onResume();
Intent intent = getIntent();
String action = intent.getAction();
- int state = mKeyStore.test();
if (ACTION_RESET.equals(action)) {
- showResetDialog();
- } else if (ACTION_SET_PASSWORD.equals(action)) {
- showPasswordDialog(state == KeyStore.UNINITIALIZED);
+ new ResetDialog();
} else {
if (ACTION_INSTALL.equals(action) &&
"com.android.certinstaller".equals(getCallingPackage())) {
- mBundle = intent.getExtras();
- }
- if (state == KeyStore.UNINITIALIZED) {
- showPasswordDialog(true);
- } else if (state == KeyStore.LOCKED) {
- showUnlockDialog();
- } else {
- install();
- finish();
+ mInstallBundle = intent.getExtras();
}
+ // ACTION_UNLOCK also handled here in addition to ACTION_INSTALL
+ handleUnlockOrInstall();
}
}
- private void install() {
- if (mBundle != null && !mBundle.isEmpty()) {
- try {
- for (String key : mBundle.keySet()) {
- byte[] value = mBundle.getByteArray(key);
- if (value != null && !mKeyStore.put(key.getBytes("UTF-8"), value)) {
- Log.e(TAG, "Failed to install " + key);
- return;
- }
+ /**
+ * Based on the current state of the KeyStore and key guard, try to
+ * make progress on unlocking or installing to the keystore.
+ */
+ private void handleUnlockOrInstall() {
+ // something already decided we are done, do not proceed
+ if (isFinishing()) {
+ return;
+ }
+ switch (mKeyStore.state()) {
+ case UNINITIALIZED: {
+ ensureKeyGuard();
+ return;
+ }
+ case LOCKED: {
+ new UnlockDialog();
+ return;
+ }
+ case UNLOCKED: {
+ if (!checkKeyGuardQuality()) {
+ new ConfigureKeyGuardDialog();
+ return;
}
- setResult(RESULT_OK);
- } catch (UnsupportedEncodingException e) {
- // Should never happen.
- throw new RuntimeException(e);
+ installIfAvailable();
+ finish();
+ return;
}
}
}
- private void showResetDialog() {
- AlertDialog dialog = new AlertDialog.Builder(this)
- .setTitle(android.R.string.dialog_alert_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(R.string.credentials_reset_hint)
- .setNeutralButton(android.R.string.ok, this)
- .setNegativeButton(android.R.string.cancel, this)
- .create();
- dialog.setOnDismissListener(this);
- dialog.show();
+ /**
+ * Make sure the user enters the key guard to set or change the
+ * keystore password. This can be used in UNINITIALIZED to set the
+ * keystore password or UNLOCKED to change the password (as is the
+ * case after unlocking with an old-style password).
+ */
+ private void ensureKeyGuard() {
+ if (!checkKeyGuardQuality()) {
+ // key guard not setup, doing so will initialize keystore
+ new ConfigureKeyGuardDialog();
+ // will return to onResume after Activity
+ return;
+ }
+ // force key guard confirmation
+ if (confirmKeyGuard()) {
+ // will return password value via onActivityResult
+ return;
+ }
+ finish();
}
- private void showPasswordDialog(boolean firstTime) {
- View view = View.inflate(this, R.layout.credentials_dialog, null);
+ /**
+ * Returns true if the currently set key guard matches our minimum quality requirements.
+ */
+ private boolean checkKeyGuardQuality() {
+ int quality = new LockPatternUtils(this).getActivePasswordQuality();
+ return (quality >= MIN_PASSWORD_QUALITY);
+ }
- ((TextView) view.findViewById(R.id.hint)).setText(R.string.credentials_password_hint);
- if (!firstTime) {
- view.findViewById(R.id.old_password_prompt).setVisibility(View.VISIBLE);
- mOldPassword = (TextView) view.findViewById(R.id.old_password);
- mOldPassword.setVisibility(View.VISIBLE);
- mOldPassword.addTextChangedListener(this);
+ /**
+ * Install credentials if available, otherwise do nothing.
+ */
+ private void installIfAvailable() {
+ if (mInstallBundle != null && !mInstallBundle.isEmpty()) {
+ Bundle bundle = mInstallBundle;
+ mInstallBundle = null;
+ for (String key : bundle.keySet()) {
+ byte[] value = bundle.getByteArray(key);
+ if (value != null && !mKeyStore.put(key, value)) {
+ Log.e(TAG, "Failed to install " + key);
+ return;
+ }
+ }
+ setResult(RESULT_OK);
}
- view.findViewById(R.id.new_passwords).setVisibility(View.VISIBLE);
- mNewPassword = (TextView) view.findViewById(R.id.new_password);
- mNewPassword.addTextChangedListener(this);
- mConfirmPassword = (TextView) view.findViewById(R.id.confirm_password);
- mConfirmPassword.addTextChangedListener(this);
- mError = (TextView) view.findViewById(R.id.error);
-
- AlertDialog dialog = new AlertDialog.Builder(this)
- .setView(view)
- .setTitle(R.string.credentials_set_password)
- .setPositiveButton(android.R.string.ok, this)
- .setNegativeButton(android.R.string.cancel, this)
- .create();
- dialog.setOnDismissListener(this);
- dialog.show();
- mButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
- mButton.setEnabled(false);
}
- private void showUnlockDialog() {
- View view = View.inflate(this, R.layout.credentials_dialog, null);
-
- ((TextView) view.findViewById(R.id.hint)).setText(R.string.credentials_unlock_hint);
- mOldPassword = (TextView) view.findViewById(R.id.old_password);
- mOldPassword.setVisibility(View.VISIBLE);
- mOldPassword.addTextChangedListener(this);
- mError = (TextView) view.findViewById(R.id.error);
-
- AlertDialog dialog = new AlertDialog.Builder(this)
- .setView(view)
- .setTitle(R.string.credentials_unlock)
- .setPositiveButton(android.R.string.ok, this)
- .setNegativeButton(android.R.string.cancel, this)
- .create();
- dialog.setOnDismissListener(this);
- dialog.show();
- mButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
- mButton.setEnabled(false);
- }
+ /**
+ * Prompt for reset confirmation, resetting on confirmation, finishing otherwise.
+ */
+ private class ResetDialog
+ implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener
+ {
+ private boolean mResetConfirmed;
- public void afterTextChanged(Editable editable) {
- if ((mOldPassword == null || mOldPassword.getText().length() > 0) &&
- (mNewPassword == null || mNewPassword.getText().length() >= 8) &&
- (mConfirmPassword == null || mConfirmPassword.getText().length() >= 8)) {
- mButton.setEnabled(true);
- } else {
- mButton.setEnabled(false);
+ private ResetDialog() {
+ AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
+ .setTitle(android.R.string.dialog_alert_title)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(R.string.credentials_reset_hint)
+ .setPositiveButton(android.R.string.ok, this)
+ .setNegativeButton(android.R.string.cancel, this)
+ .create();
+ dialog.setOnDismissListener(this);
+ dialog.show();
}
- }
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- }
+ @Override public void onClick(DialogInterface dialog, int button) {
+ mResetConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
+ }
- public void onTextChanged(CharSequence s,int start, int before, int count) {
+ @Override public void onDismiss(DialogInterface dialog) {
+ if (mResetConfirmed) {
+ mResetConfirmed = false;
+ new ResetKeyStoreAndKeyChain().execute();
+ return;
+ }
+ finish();
+ }
}
- public void onClick(DialogInterface dialog, int button) {
- mSubmit = (button == DialogInterface.BUTTON_POSITIVE);
- if (button == DialogInterface.BUTTON_NEUTRAL) {
+ /**
+ * Background task to handle reset of both keystore and user installed CAs.
+ */
+ private class ResetKeyStoreAndKeyChain extends AsyncTask<Void, Void, Boolean> {
+
+ @Override protected Boolean doInBackground(Void... unused) {
+
mKeyStore.reset();
- Toast.makeText(this, R.string.credentials_erased, Toast.LENGTH_SHORT).show();
+
+ try {
+ KeyChainConnection keyChainConnection = KeyChain.bind(CredentialStorage.this);
+ try {
+ return keyChainConnection.getService().reset();
+ } catch (RemoteException e) {
+ return false;
+ } finally {
+ keyChainConnection.close();
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return false;
+ }
+ }
+
+ @Override protected void onPostExecute(Boolean success) {
+ if (success) {
+ Toast.makeText(CredentialStorage.this,
+ R.string.credentials_erased, Toast.LENGTH_SHORT).show();
+ } else {
+ Toast.makeText(CredentialStorage.this,
+ R.string.credentials_not_erased, Toast.LENGTH_SHORT).show();
+ }
+ finish();
}
}
- public void onDismiss(DialogInterface dialog) {
- if (mSubmit) {
- mSubmit = false;
- mError.setVisibility(View.VISIBLE);
+ /**
+ * Prompt for key guard configuration confirmation.
+ */
+ private class ConfigureKeyGuardDialog
+ implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener
+ {
+ private boolean mConfigureConfirmed;
- if (mNewPassword == null) {
- mKeyStore.unlock(mOldPassword.getText().toString());
- } else {
- String newPassword = mNewPassword.getText().toString();
- String confirmPassword = mConfirmPassword.getText().toString();
+ private ConfigureKeyGuardDialog() {
+ AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
+ .setTitle(android.R.string.dialog_alert_title)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(R.string.credentials_configure_lock_screen_hint)
+ .setPositiveButton(android.R.string.ok, this)
+ .setNegativeButton(android.R.string.cancel, this)
+ .create();
+ dialog.setOnDismissListener(this);
+ dialog.show();
+ }
+
+ @Override public void onClick(DialogInterface dialog, int button) {
+ mConfigureConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
+ }
- if (!newPassword.equals(confirmPassword)) {
- mError.setText(R.string.credentials_passwords_mismatch);
- ((AlertDialog) dialog).show();
+ @Override public void onDismiss(DialogInterface dialog) {
+ if (mConfigureConfirmed) {
+ mConfigureConfirmed = false;
+ Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
+ intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY,
+ MIN_PASSWORD_QUALITY);
+ startActivity(intent);
+ return;
+ }
+ finish();
+ }
+ }
+
+ /**
+ * Confirm existing key guard, returning password via onActivityResult.
+ */
+ private boolean confirmKeyGuard() {
+ Resources res = getResources();
+ boolean launched = new ChooseLockSettingsHelper(this)
+ .launchConfirmationActivity(CONFIRM_KEY_GUARD_REQUEST,
+ res.getText(R.string.master_clear_gesture_prompt),
+ res.getText(R.string.master_clear_gesture_explanation));
+ return launched;
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ /**
+ * Receive key guard password initiated by confirmKeyGuard.
+ */
+ if (requestCode == CONFIRM_KEY_GUARD_REQUEST) {
+ if (resultCode == Activity.RESULT_OK) {
+ String password = data.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
+ if (!TextUtils.isEmpty(password)) {
+ // success
+ mKeyStore.password(password);
+ // return to onResume
return;
- } else if (mOldPassword == null) {
- mKeyStore.password(newPassword);
- } else {
- mKeyStore.password(mOldPassword.getText().toString(), newPassword);
}
}
+ // failed confirmation, bail
+ finish();
+ }
+ }
+
+ /**
+ * Prompt for unlock with old-style password.
+ *
+ * On successful unlock, ensure migration to key guard before continuing.
+ * On unsuccessful unlock, retry by calling handleUnlockOrInstall.
+ */
+ private class UnlockDialog implements TextWatcher,
+ DialogInterface.OnClickListener, DialogInterface.OnDismissListener
+ {
+ private boolean mUnlockConfirmed;
+
+ private final Button mButton;
+ private final TextView mOldPassword;
+ private final TextView mError;
+
+ private UnlockDialog() {
+ View view = View.inflate(CredentialStorage.this, R.layout.credentials_dialog, null);
+
+ CharSequence text;
+ if (mRetriesRemaining == -1) {
+ text = getResources().getText(R.string.credentials_unlock_hint);
+ } else if (mRetriesRemaining > 3) {
+ text = getResources().getText(R.string.credentials_wrong_password);
+ } else if (mRetriesRemaining == 1) {
+ text = getResources().getText(R.string.credentials_reset_warning);
+ } else {
+ text = getString(R.string.credentials_reset_warning_plural, mRetriesRemaining);
+ }
- int error = mKeyStore.getLastError();
- if (error == KeyStore.NO_ERROR) {
- Toast.makeText(this, R.string.credentials_enabled, Toast.LENGTH_SHORT).show();
- install();
- } else if (error == KeyStore.UNINITIALIZED) {
- Toast.makeText(this, R.string.credentials_erased, Toast.LENGTH_SHORT).show();
- } else if (error >= KeyStore.WRONG_PASSWORD) {
- int count = error - KeyStore.WRONG_PASSWORD + 1;
- if (count > 3) {
- mError.setText(R.string.credentials_wrong_password);
- } else if (count == 1) {
- mError.setText(R.string.credentials_reset_warning);
- } else {
- mError.setText(getString(R.string.credentials_reset_warning_plural, count));
+ ((TextView) view.findViewById(R.id.hint)).setText(text);
+ mOldPassword = (TextView) view.findViewById(R.id.old_password);
+ mOldPassword.setVisibility(View.VISIBLE);
+ mOldPassword.addTextChangedListener(this);
+ mError = (TextView) view.findViewById(R.id.error);
+
+ AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
+ .setView(view)
+ .setTitle(R.string.credentials_unlock)
+ .setPositiveButton(android.R.string.ok, this)
+ .setNegativeButton(android.R.string.cancel, this)
+ .create();
+ dialog.setOnDismissListener(this);
+ dialog.show();
+ mButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
+ mButton.setEnabled(false);
+ }
+
+ @Override public void afterTextChanged(Editable editable) {
+ mButton.setEnabled(mOldPassword == null || mOldPassword.getText().length() > 0);
+ }
+
+ @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override public void onTextChanged(CharSequence s,int start, int before, int count) {
+ }
+
+ @Override public void onClick(DialogInterface dialog, int button) {
+ mUnlockConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
+ }
+
+ @Override public void onDismiss(DialogInterface dialog) {
+ if (mUnlockConfirmed) {
+ mUnlockConfirmed = false;
+ mError.setVisibility(View.VISIBLE);
+ mKeyStore.unlock(mOldPassword.getText().toString());
+ int error = mKeyStore.getLastError();
+ if (error == KeyStore.NO_ERROR) {
+ mRetriesRemaining = -1;
+ Toast.makeText(CredentialStorage.this,
+ R.string.credentials_enabled,
+ Toast.LENGTH_SHORT).show();
+ // aha, now we are unlocked, switch to key guard.
+ // we'll end up back in onResume to install
+ ensureKeyGuard();
+ } else if (error == KeyStore.UNINITIALIZED) {
+ mRetriesRemaining = -1;
+ Toast.makeText(CredentialStorage.this,
+ R.string.credentials_erased,
+ Toast.LENGTH_SHORT).show();
+ // we are reset, we can now set new password with key guard
+ handleUnlockOrInstall();
+ } else if (error >= KeyStore.WRONG_PASSWORD) {
+ // we need to try again
+ mRetriesRemaining = error - KeyStore.WRONG_PASSWORD + 1;
+ handleUnlockOrInstall();
}
- ((AlertDialog) dialog).show();
return;
}
+ finish();
}
- finish();
}
}