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