Home | History | Annotate | Download | only in settings
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.settings;
     18 
     19 import android.app.Activity;
     20 import android.app.AlertDialog;
     21 import android.app.admin.DevicePolicyManager;
     22 import android.content.DialogInterface;
     23 import android.content.Intent;
     24 import android.content.res.Resources;
     25 import android.os.AsyncTask;
     26 import android.os.Bundle;
     27 import android.os.RemoteException;
     28 import android.security.KeyChain.KeyChainConnection;
     29 import android.security.KeyChain;
     30 import android.security.KeyStore;
     31 import android.text.Editable;
     32 import android.text.TextUtils;
     33 import android.text.TextWatcher;
     34 import android.util.Log;
     35 import android.view.View;
     36 import android.widget.Button;
     37 import android.widget.TextView;
     38 import android.widget.Toast;
     39 import com.android.internal.widget.LockPatternUtils;
     40 
     41 /**
     42  * CredentialStorage handles KeyStore reset, unlock, and install.
     43  *
     44  * CredentialStorage has a pretty convoluted state machine to migrate
     45  * from the old style separate keystore password to a new key guard
     46  * based password, as well as to deal with setting up the key guard if
     47  * necessary.
     48  *
     49  * KeyStore: UNINITALIZED
     50  * KeyGuard: OFF
     51  * Action:   set up key guard
     52  * Notes:    factory state
     53  *
     54  * KeyStore: UNINITALIZED
     55  * KeyGuard: ON
     56  * Action:   confirm key guard
     57  * Notes:    user had key guard but no keystore and upgraded from pre-ICS
     58  *           OR user had key guard and pre-ICS keystore password which was then reset
     59  *
     60  * KeyStore: LOCKED
     61  * KeyGuard: OFF/ON
     62  * Action:   old unlock dialog
     63  * Notes:    assume old password, need to use it to unlock.
     64  *           if unlock, ensure key guard before install.
     65  *           if reset, treat as UNINITALIZED/OFF
     66  *
     67  * KeyStore: UNLOCKED
     68  * KeyGuard: OFF
     69  * Action:   set up key guard
     70  * Notes:    ensure key guard, then proceed
     71  *
     72  * KeyStore: UNLOCKED
     73  * keyguard: ON
     74  * Action:   normal unlock/install
     75  * Notes:    this is the common case
     76  */
     77 public final class CredentialStorage extends Activity {
     78 
     79     private static final String TAG = "CredentialStorage";
     80 
     81     public static final String ACTION_UNLOCK = "com.android.credentials.UNLOCK";
     82     public static final String ACTION_INSTALL = "com.android.credentials.INSTALL";
     83     public static final String ACTION_RESET = "com.android.credentials.RESET";
     84 
     85     // This is the minimum acceptable password quality.  If the current password quality is
     86     // lower than this, keystore should not be activated.
     87     static final int MIN_PASSWORD_QUALITY = DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
     88 
     89     private static final int CONFIRM_KEY_GUARD_REQUEST = 1;
     90 
     91     private final KeyStore mKeyStore = KeyStore.getInstance();
     92 
     93     /**
     94      * When non-null, the bundle containing credentials to install.
     95      */
     96     private Bundle mInstallBundle;
     97 
     98     /**
     99      * After unsuccessful KeyStore.unlock, the number of unlock
    100      * attempts remaining before the KeyStore will reset itself.
    101      *
    102      * Reset to -1 on successful unlock or reset.
    103      */
    104     private int mRetriesRemaining = -1;
    105 
    106     @Override protected void onResume() {
    107         super.onResume();
    108 
    109         Intent intent = getIntent();
    110         String action = intent.getAction();
    111 
    112         if (ACTION_RESET.equals(action)) {
    113             new ResetDialog();
    114         } else {
    115             if (ACTION_INSTALL.equals(action) &&
    116                     "com.android.certinstaller".equals(getCallingPackage())) {
    117                 mInstallBundle = intent.getExtras();
    118             }
    119             // ACTION_UNLOCK also handled here in addition to ACTION_INSTALL
    120             handleUnlockOrInstall();
    121         }
    122     }
    123 
    124     /**
    125      * Based on the current state of the KeyStore and key guard, try to
    126      * make progress on unlocking or installing to the keystore.
    127      */
    128     private void handleUnlockOrInstall() {
    129         // something already decided we are done, do not proceed
    130         if (isFinishing()) {
    131             return;
    132         }
    133         switch (mKeyStore.state()) {
    134             case UNINITIALIZED: {
    135                 ensureKeyGuard();
    136                 return;
    137             }
    138             case LOCKED: {
    139                 new UnlockDialog();
    140                 return;
    141             }
    142             case UNLOCKED: {
    143                 if (!checkKeyGuardQuality()) {
    144                     new ConfigureKeyGuardDialog();
    145                     return;
    146                 }
    147                 installIfAvailable();
    148                 finish();
    149                 return;
    150             }
    151         }
    152     }
    153 
    154     /**
    155      * Make sure the user enters the key guard to set or change the
    156      * keystore password. This can be used in UNINITIALIZED to set the
    157      * keystore password or UNLOCKED to change the password (as is the
    158      * case after unlocking with an old-style password).
    159      */
    160     private void ensureKeyGuard() {
    161         if (!checkKeyGuardQuality()) {
    162             // key guard not setup, doing so will initialize keystore
    163             new ConfigureKeyGuardDialog();
    164             // will return to onResume after Activity
    165             return;
    166         }
    167         // force key guard confirmation
    168         if (confirmKeyGuard()) {
    169             // will return password value via onActivityResult
    170             return;
    171         }
    172         finish();
    173     }
    174 
    175     /**
    176      * Returns true if the currently set key guard matches our minimum quality requirements.
    177      */
    178     private boolean checkKeyGuardQuality() {
    179         int quality = new LockPatternUtils(this).getActivePasswordQuality();
    180         return (quality >= MIN_PASSWORD_QUALITY);
    181     }
    182 
    183     /**
    184      * Install credentials if available, otherwise do nothing.
    185      */
    186     private void installIfAvailable() {
    187         if (mInstallBundle != null && !mInstallBundle.isEmpty()) {
    188             Bundle bundle = mInstallBundle;
    189             mInstallBundle = null;
    190             for (String key : bundle.keySet()) {
    191                 byte[] value = bundle.getByteArray(key);
    192                 if (value != null && !mKeyStore.put(key, value)) {
    193                     Log.e(TAG, "Failed to install " + key);
    194                     return;
    195                 }
    196             }
    197             setResult(RESULT_OK);
    198         }
    199     }
    200 
    201     /**
    202      * Prompt for reset confirmation, resetting on confirmation, finishing otherwise.
    203      */
    204     private class ResetDialog
    205             implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener
    206     {
    207         private boolean mResetConfirmed;
    208 
    209         private ResetDialog() {
    210             AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
    211                     .setTitle(android.R.string.dialog_alert_title)
    212                     .setIcon(android.R.drawable.ic_dialog_alert)
    213                     .setMessage(R.string.credentials_reset_hint)
    214                     .setPositiveButton(android.R.string.ok, this)
    215                     .setNegativeButton(android.R.string.cancel, this)
    216                     .create();
    217             dialog.setOnDismissListener(this);
    218             dialog.show();
    219         }
    220 
    221         @Override public void onClick(DialogInterface dialog, int button) {
    222             mResetConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
    223         }
    224 
    225         @Override public void onDismiss(DialogInterface dialog) {
    226             if (mResetConfirmed) {
    227                 mResetConfirmed = false;
    228                 new ResetKeyStoreAndKeyChain().execute();
    229                 return;
    230             }
    231             finish();
    232         }
    233     }
    234 
    235     /**
    236      * Background task to handle reset of both keystore and user installed CAs.
    237      */
    238     private class ResetKeyStoreAndKeyChain extends AsyncTask<Void, Void, Boolean> {
    239 
    240         @Override protected Boolean doInBackground(Void... unused) {
    241 
    242             mKeyStore.reset();
    243 
    244             try {
    245                 KeyChainConnection keyChainConnection = KeyChain.bind(CredentialStorage.this);
    246                 try {
    247                     return keyChainConnection.getService().reset();
    248                 } catch (RemoteException e) {
    249                     return false;
    250                 } finally {
    251                     keyChainConnection.close();
    252                 }
    253             } catch (InterruptedException e) {
    254                 Thread.currentThread().interrupt();
    255                 return false;
    256             }
    257         }
    258 
    259         @Override protected void onPostExecute(Boolean success) {
    260             if (success) {
    261                 Toast.makeText(CredentialStorage.this,
    262                                R.string.credentials_erased, Toast.LENGTH_SHORT).show();
    263             } else {
    264                 Toast.makeText(CredentialStorage.this,
    265                                R.string.credentials_not_erased, Toast.LENGTH_SHORT).show();
    266             }
    267             finish();
    268         }
    269     }
    270 
    271     /**
    272      * Prompt for key guard configuration confirmation.
    273      */
    274     private class ConfigureKeyGuardDialog
    275             implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener
    276     {
    277         private boolean mConfigureConfirmed;
    278 
    279         private ConfigureKeyGuardDialog() {
    280             AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
    281                     .setTitle(android.R.string.dialog_alert_title)
    282                     .setIcon(android.R.drawable.ic_dialog_alert)
    283                     .setMessage(R.string.credentials_configure_lock_screen_hint)
    284                     .setPositiveButton(android.R.string.ok, this)
    285                     .setNegativeButton(android.R.string.cancel, this)
    286                     .create();
    287             dialog.setOnDismissListener(this);
    288             dialog.show();
    289         }
    290 
    291         @Override public void onClick(DialogInterface dialog, int button) {
    292             mConfigureConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
    293         }
    294 
    295         @Override public void onDismiss(DialogInterface dialog) {
    296             if (mConfigureConfirmed) {
    297                 mConfigureConfirmed = false;
    298                 Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
    299                 intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY,
    300                                 MIN_PASSWORD_QUALITY);
    301                 startActivity(intent);
    302                 return;
    303             }
    304             finish();
    305         }
    306     }
    307 
    308     /**
    309      * Confirm existing key guard, returning password via onActivityResult.
    310      */
    311     private boolean confirmKeyGuard() {
    312         Resources res = getResources();
    313         boolean launched = new ChooseLockSettingsHelper(this)
    314                 .launchConfirmationActivity(CONFIRM_KEY_GUARD_REQUEST,
    315                                             res.getText(R.string.master_clear_gesture_prompt),
    316                                             res.getText(R.string.master_clear_gesture_explanation));
    317         return launched;
    318     }
    319 
    320     @Override
    321     public void onActivityResult(int requestCode, int resultCode, Intent data) {
    322         super.onActivityResult(requestCode, resultCode, data);
    323 
    324         /**
    325          * Receive key guard password initiated by confirmKeyGuard.
    326          */
    327         if (requestCode == CONFIRM_KEY_GUARD_REQUEST) {
    328             if (resultCode == Activity.RESULT_OK) {
    329                 String password = data.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
    330                 if (!TextUtils.isEmpty(password)) {
    331                     // success
    332                     mKeyStore.password(password);
    333                     // return to onResume
    334                     return;
    335                 }
    336             }
    337             // failed confirmation, bail
    338             finish();
    339         }
    340     }
    341 
    342     /**
    343      * Prompt for unlock with old-style password.
    344      *
    345      * On successful unlock, ensure migration to key guard before continuing.
    346      * On unsuccessful unlock, retry by calling handleUnlockOrInstall.
    347      */
    348     private class UnlockDialog implements TextWatcher,
    349             DialogInterface.OnClickListener, DialogInterface.OnDismissListener
    350     {
    351         private boolean mUnlockConfirmed;
    352 
    353         private final Button mButton;
    354         private final TextView mOldPassword;
    355         private final TextView mError;
    356 
    357         private UnlockDialog() {
    358             View view = View.inflate(CredentialStorage.this, R.layout.credentials_dialog, null);
    359 
    360             CharSequence text;
    361             if (mRetriesRemaining == -1) {
    362                 text = getResources().getText(R.string.credentials_unlock_hint);
    363             } else if (mRetriesRemaining > 3) {
    364                 text = getResources().getText(R.string.credentials_wrong_password);
    365             } else if (mRetriesRemaining == 1) {
    366                 text = getResources().getText(R.string.credentials_reset_warning);
    367             } else {
    368                 text = getString(R.string.credentials_reset_warning_plural, mRetriesRemaining);
    369             }
    370 
    371             ((TextView) view.findViewById(R.id.hint)).setText(text);
    372             mOldPassword = (TextView) view.findViewById(R.id.old_password);
    373             mOldPassword.setVisibility(View.VISIBLE);
    374             mOldPassword.addTextChangedListener(this);
    375             mError = (TextView) view.findViewById(R.id.error);
    376 
    377             AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
    378                     .setView(view)
    379                     .setTitle(R.string.credentials_unlock)
    380                     .setPositiveButton(android.R.string.ok, this)
    381                     .setNegativeButton(android.R.string.cancel, this)
    382                     .create();
    383             dialog.setOnDismissListener(this);
    384             dialog.show();
    385             mButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
    386             mButton.setEnabled(false);
    387         }
    388 
    389         @Override public void afterTextChanged(Editable editable) {
    390             mButton.setEnabled(mOldPassword == null || mOldPassword.getText().length() > 0);
    391         }
    392 
    393         @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    394         }
    395 
    396         @Override public void onTextChanged(CharSequence s,int start, int before, int count) {
    397         }
    398 
    399         @Override public void onClick(DialogInterface dialog, int button) {
    400             mUnlockConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
    401         }
    402 
    403         @Override public void onDismiss(DialogInterface dialog) {
    404             if (mUnlockConfirmed) {
    405                 mUnlockConfirmed = false;
    406                 mError.setVisibility(View.VISIBLE);
    407                 mKeyStore.unlock(mOldPassword.getText().toString());
    408                 int error = mKeyStore.getLastError();
    409                 if (error == KeyStore.NO_ERROR) {
    410                     mRetriesRemaining = -1;
    411                     Toast.makeText(CredentialStorage.this,
    412                                    R.string.credentials_enabled,
    413                                    Toast.LENGTH_SHORT).show();
    414                     // aha, now we are unlocked, switch to key guard.
    415                     // we'll end up back in onResume to install
    416                     ensureKeyGuard();
    417                 } else if (error == KeyStore.UNINITIALIZED) {
    418                     mRetriesRemaining = -1;
    419                     Toast.makeText(CredentialStorage.this,
    420                                    R.string.credentials_erased,
    421                                    Toast.LENGTH_SHORT).show();
    422                     // we are reset, we can now set new password with key guard
    423                     handleUnlockOrInstall();
    424                 } else if (error >= KeyStore.WRONG_PASSWORD) {
    425                     // we need to try again
    426                     mRetriesRemaining = error - KeyStore.WRONG_PASSWORD + 1;
    427                     handleUnlockOrInstall();
    428                 }
    429                 return;
    430             }
    431             finish();
    432         }
    433     }
    434 }
    435