diff options
Diffstat (limited to 'src/com/android/settings/CredentialStorage.java')
-rw-r--r-- | src/com/android/settings/CredentialStorage.java | 492 |
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(); } } |