Files
app_Settings/src/com/android/settings/CredentialStorage.java
Brian Carlstrom 0e88f4dd19 Bulletproof CredentialStorage state handling
As noted by the class javadoc, CredentialStorage has seen the number
of cases to cope with grow. This change tries to address those cases.

	src/com/android/settings/CredentialStorage.java

Added ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD to coordinate
additional producer and consumer.

    constant declaration here, since its used by callers of
    ChooseLockSettingsHelper.launchConfirmationActivity
	src/com/android/settings/ChooseLockSettingsHelper.java

    old producer
	src/com/android/settings/ConfirmLockPassword.java
    new producer (CredentialStorage wants passwords and patterns)
	src/com/android/settings/ConfirmLockPattern.java
    new consumer
	src/com/android/settings/CredentialStorage.java
    old consumer
	src/com/android/settings/CryptKeeperSettings.java

Made class final and removed protected from method to make it clear
ChooseLockSettingsHelper is not to be used by subclassing.

	src/com/android/settings/ChooseLockSettingsHelper.java

Change-Id: Ib2d65398fe44573168a6267a0376c3b0388b16c8
2011-06-02 21:06:21 -07:00

435 lines
16 KiB
Java

/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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;
/**
* 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 {
private static final String TAG = "CredentialStorage";
public static final String ACTION_UNLOCK = "com.android.credentials.UNLOCK";
public static final String ACTION_INSTALL = "com.android.credentials.INSTALL";
public static final String ACTION_RESET = "com.android.credentials.RESET";
// 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 static final int CONFIRM_KEY_GUARD_REQUEST = 1;
private final KeyStore mKeyStore = KeyStore.getInstance();
/**
* 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();
if (ACTION_RESET.equals(action)) {
new ResetDialog();
} else {
if (ACTION_INSTALL.equals(action) &&
"com.android.certinstaller".equals(getCallingPackage())) {
mInstallBundle = intent.getExtras();
}
// ACTION_UNLOCK also handled here in addition to ACTION_INSTALL
handleUnlockOrInstall();
}
}
/**
* 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;
}
installIfAvailable();
finish();
return;
}
}
}
/**
* 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();
}
/**
* 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);
}
/**
* 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);
}
}
/**
* Prompt for reset confirmation, resetting on confirmation, finishing otherwise.
*/
private class ResetDialog
implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener
{
private boolean mResetConfirmed;
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();
}
@Override public void onClick(DialogInterface dialog, int button) {
mResetConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
}
@Override public void onDismiss(DialogInterface dialog) {
if (mResetConfirmed) {
mResetConfirmed = false;
new ResetKeyStoreAndKeyChain().execute();
return;
}
finish();
}
}
/**
* 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();
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();
}
}
/**
* Prompt for key guard configuration confirmation.
*/
private class ConfigureKeyGuardDialog
implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener
{
private boolean mConfigureConfirmed;
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);
}
@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;
}
}
// 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);
}
((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();
}
return;
}
finish();
}
}
}