2 * Copyright (C) 2011 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.settings;
19 import android.app.Activity;
20 import android.app.AlertDialog;
21 import android.app.admin.DevicePolicyManager;
22 import android.content.Context;
23 import android.content.DialogInterface;
24 import android.content.Intent;
25 import android.content.res.Resources;
26 import android.os.AsyncTask;
27 import android.os.Bundle;
28 import android.os.RemoteException;
29 import android.os.Process;
30 import android.os.UserManager;
31 import android.security.Credentials;
32 import android.security.KeyChain.KeyChainConnection;
33 import android.security.KeyChain;
34 import android.security.KeyStore;
35 import android.text.Editable;
36 import android.text.TextUtils;
37 import android.text.TextWatcher;
38 import android.util.Log;
39 import android.view.View;
40 import android.widget.Button;
41 import android.widget.TextView;
42 import android.widget.Toast;
44 import com.android.internal.widget.LockPatternUtils;
45 import com.android.org.bouncycastle.asn1.ASN1InputStream;
46 import com.android.org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
48 import org.apache.harmony.security.utils.AlgNameMapper;
50 import java.io.ByteArrayInputStream;
51 import java.io.IOException;
54 * CredentialStorage handles KeyStore reset, unlock, and install.
56 * CredentialStorage has a pretty convoluted state machine to migrate
57 * from the old style separate keystore password to a new key guard
58 * based password, as well as to deal with setting up the key guard if
61 * KeyStore: UNINITALIZED
63 * Action: set up key guard
64 * Notes: factory state
66 * KeyStore: UNINITALIZED
68 * Action: confirm key guard
69 * Notes: user had key guard but no keystore and upgraded from pre-ICS
70 * OR user had key guard and pre-ICS keystore password which was then reset
74 * Action: old unlock dialog
75 * Notes: assume old password, need to use it to unlock.
76 * if unlock, ensure key guard before install.
77 * if reset, treat as UNINITALIZED/OFF
81 * Action: set up key guard
82 * Notes: ensure key guard, then proceed
86 * Action: normal unlock/install
87 * Notes: this is the common case
89 public final class CredentialStorage extends Activity {
91 private static final String TAG = "CredentialStorage";
93 public static final String ACTION_UNLOCK = "com.android.credentials.UNLOCK";
94 public static final String ACTION_INSTALL = "com.android.credentials.INSTALL";
95 public static final String ACTION_RESET = "com.android.credentials.RESET";
97 // This is the minimum acceptable password quality. If the current password quality is
98 // lower than this, keystore should not be activated.
99 static final int MIN_PASSWORD_QUALITY = DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
101 private static final int CONFIRM_KEY_GUARD_REQUEST = 1;
103 private final KeyStore mKeyStore = KeyStore.getInstance();
106 * When non-null, the bundle containing credentials to install.
108 private Bundle mInstallBundle;
111 * After unsuccessful KeyStore.unlock, the number of unlock
112 * attempts remaining before the KeyStore will reset itself.
114 * Reset to -1 on successful unlock or reset.
116 private int mRetriesRemaining = -1;
119 protected void onResume() {
122 Intent intent = getIntent();
123 String action = intent.getAction();
124 UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
125 if (!userManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_CREDENTIALS)) {
126 if (ACTION_RESET.equals(action)) {
129 if (ACTION_INSTALL.equals(action)
130 && "com.android.certinstaller".equals(getCallingPackage())) {
131 mInstallBundle = intent.getExtras();
133 // ACTION_UNLOCK also handled here in addition to ACTION_INSTALL
134 handleUnlockOrInstall();
142 * Based on the current state of the KeyStore and key guard, try to
143 * make progress on unlocking or installing to the keystore.
145 private void handleUnlockOrInstall() {
146 // something already decided we are done, do not proceed
150 switch (mKeyStore.state()) {
151 case UNINITIALIZED: {
160 if (!checkKeyGuardQuality()) {
161 new ConfigureKeyGuardDialog();
164 installIfAvailable();
172 * Make sure the user enters the key guard to set or change the
173 * keystore password. This can be used in UNINITIALIZED to set the
174 * keystore password or UNLOCKED to change the password (as is the
175 * case after unlocking with an old-style password).
177 private void ensureKeyGuard() {
178 if (!checkKeyGuardQuality()) {
179 // key guard not setup, doing so will initialize keystore
180 new ConfigureKeyGuardDialog();
181 // will return to onResume after Activity
184 // force key guard confirmation
185 if (confirmKeyGuard()) {
186 // will return password value via onActivityResult
193 * Returns true if the currently set key guard matches our minimum quality requirements.
195 private boolean checkKeyGuardQuality() {
196 int quality = new LockPatternUtils(this).getActivePasswordQuality();
197 return (quality >= MIN_PASSWORD_QUALITY);
200 private boolean isHardwareBackedKey(byte[] keyData) {
202 ASN1InputStream bIn = new ASN1InputStream(new ByteArrayInputStream(keyData));
203 PrivateKeyInfo pki = PrivateKeyInfo.getInstance(bIn.readObject());
204 String algId = pki.getAlgorithmId().getAlgorithm().getId();
205 String algName = AlgNameMapper.map2AlgName(algId);
207 return KeyChain.isBoundKeyAlgorithm(algName);
208 } catch (IOException e) {
209 Log.e(TAG, "Failed to parse key data");
215 * Install credentials if available, otherwise do nothing.
217 private void installIfAvailable() {
218 if (mInstallBundle != null && !mInstallBundle.isEmpty()) {
219 Bundle bundle = mInstallBundle;
220 mInstallBundle = null;
222 final int uid = bundle.getInt(Credentials.EXTRA_INSTALL_AS_UID, -1);
224 if (bundle.containsKey(Credentials.EXTRA_USER_PRIVATE_KEY_NAME)) {
225 String key = bundle.getString(Credentials.EXTRA_USER_PRIVATE_KEY_NAME);
226 byte[] value = bundle.getByteArray(Credentials.EXTRA_USER_PRIVATE_KEY_DATA);
228 int flags = KeyStore.FLAG_ENCRYPTED;
229 if (uid == Process.WIFI_UID && isHardwareBackedKey(value)) {
230 // Hardware backed keystore is secure enough to allow for WIFI stack
231 // to enable access to secure networks without user intervention
232 Log.d(TAG, "Saving private key with FLAG_NONE for WIFI_UID");
233 flags = KeyStore.FLAG_NONE;
236 if (!mKeyStore.importKey(key, value, uid, flags)) {
237 Log.e(TAG, "Failed to install " + key + " as user " + uid);
242 int flags = (uid == Process.WIFI_UID) ? KeyStore.FLAG_NONE : KeyStore.FLAG_ENCRYPTED;
244 if (bundle.containsKey(Credentials.EXTRA_USER_CERTIFICATE_NAME)) {
245 String certName = bundle.getString(Credentials.EXTRA_USER_CERTIFICATE_NAME);
246 byte[] certData = bundle.getByteArray(Credentials.EXTRA_USER_CERTIFICATE_DATA);
248 if (!mKeyStore.put(certName, certData, uid, flags)) {
249 Log.e(TAG, "Failed to install " + certName + " as user " + uid);
254 if (bundle.containsKey(Credentials.EXTRA_CA_CERTIFICATES_NAME)) {
255 String caListName = bundle.getString(Credentials.EXTRA_CA_CERTIFICATES_NAME);
256 byte[] caListData = bundle.getByteArray(Credentials.EXTRA_CA_CERTIFICATES_DATA);
258 if (!mKeyStore.put(caListName, caListData, uid, flags)) {
259 Log.e(TAG, "Failed to install " + caListName + " as user " + uid);
264 setResult(RESULT_OK);
269 * Prompt for reset confirmation, resetting on confirmation, finishing otherwise.
271 private class ResetDialog
272 implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener
274 private boolean mResetConfirmed;
276 private ResetDialog() {
277 AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
278 .setTitle(android.R.string.dialog_alert_title)
279 .setMessage(R.string.credentials_reset_hint)
280 .setPositiveButton(android.R.string.ok, this)
281 .setNegativeButton(android.R.string.cancel, this)
283 dialog.setOnDismissListener(this);
287 @Override public void onClick(DialogInterface dialog, int button) {
288 mResetConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
291 @Override public void onDismiss(DialogInterface dialog) {
292 if (mResetConfirmed) {
293 mResetConfirmed = false;
294 new ResetKeyStoreAndKeyChain().execute();
302 * Background task to handle reset of both keystore and user installed CAs.
304 private class ResetKeyStoreAndKeyChain extends AsyncTask<Void, Void, Boolean> {
306 @Override protected Boolean doInBackground(Void... unused) {
311 KeyChainConnection keyChainConnection = KeyChain.bind(CredentialStorage.this);
313 return keyChainConnection.getService().reset();
314 } catch (RemoteException e) {
317 keyChainConnection.close();
319 } catch (InterruptedException e) {
320 Thread.currentThread().interrupt();
325 @Override protected void onPostExecute(Boolean success) {
327 Toast.makeText(CredentialStorage.this,
328 R.string.credentials_erased, Toast.LENGTH_SHORT).show();
330 Toast.makeText(CredentialStorage.this,
331 R.string.credentials_not_erased, Toast.LENGTH_SHORT).show();
338 * Prompt for key guard configuration confirmation.
340 private class ConfigureKeyGuardDialog
341 implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener
343 private boolean mConfigureConfirmed;
345 private ConfigureKeyGuardDialog() {
346 AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
347 .setTitle(android.R.string.dialog_alert_title)
348 .setMessage(R.string.credentials_configure_lock_screen_hint)
349 .setPositiveButton(android.R.string.ok, this)
350 .setNegativeButton(android.R.string.cancel, this)
352 dialog.setOnDismissListener(this);
356 @Override public void onClick(DialogInterface dialog, int button) {
357 mConfigureConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
360 @Override public void onDismiss(DialogInterface dialog) {
361 if (mConfigureConfirmed) {
362 mConfigureConfirmed = false;
363 Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
364 intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY,
365 MIN_PASSWORD_QUALITY);
366 startActivity(intent);
374 * Confirm existing key guard, returning password via onActivityResult.
376 private boolean confirmKeyGuard() {
377 Resources res = getResources();
378 boolean launched = new ChooseLockSettingsHelper(this)
379 .launchConfirmationActivity(CONFIRM_KEY_GUARD_REQUEST,
380 res.getText(R.string.credentials_install_gesture_prompt),
381 res.getText(R.string.credentials_install_gesture_explanation),
387 public void onActivityResult(int requestCode, int resultCode, Intent data) {
388 super.onActivityResult(requestCode, resultCode, data);
391 * Receive key guard password initiated by confirmKeyGuard.
393 if (requestCode == CONFIRM_KEY_GUARD_REQUEST) {
394 if (resultCode == Activity.RESULT_OK) {
395 String password = data.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
396 if (!TextUtils.isEmpty(password)) {
398 mKeyStore.password(password);
399 // return to onResume
403 // failed confirmation, bail
409 * Prompt for unlock with old-style password.
411 * On successful unlock, ensure migration to key guard before continuing.
412 * On unsuccessful unlock, retry by calling handleUnlockOrInstall.
414 private class UnlockDialog implements TextWatcher,
415 DialogInterface.OnClickListener, DialogInterface.OnDismissListener
417 private boolean mUnlockConfirmed;
419 private final Button mButton;
420 private final TextView mOldPassword;
421 private final TextView mError;
423 private UnlockDialog() {
424 View view = View.inflate(CredentialStorage.this, R.layout.credentials_dialog, null);
427 if (mRetriesRemaining == -1) {
428 text = getResources().getText(R.string.credentials_unlock_hint);
429 } else if (mRetriesRemaining > 3) {
430 text = getResources().getText(R.string.credentials_wrong_password);
431 } else if (mRetriesRemaining == 1) {
432 text = getResources().getText(R.string.credentials_reset_warning);
434 text = getString(R.string.credentials_reset_warning_plural, mRetriesRemaining);
437 ((TextView) view.findViewById(R.id.hint)).setText(text);
438 mOldPassword = (TextView) view.findViewById(R.id.old_password);
439 mOldPassword.setVisibility(View.VISIBLE);
440 mOldPassword.addTextChangedListener(this);
441 mError = (TextView) view.findViewById(R.id.error);
443 AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
445 .setTitle(R.string.credentials_unlock)
446 .setPositiveButton(android.R.string.ok, this)
447 .setNegativeButton(android.R.string.cancel, this)
449 dialog.setOnDismissListener(this);
451 mButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
452 mButton.setEnabled(false);
455 @Override public void afterTextChanged(Editable editable) {
456 mButton.setEnabled(mOldPassword == null || mOldPassword.getText().length() > 0);
459 @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {
462 @Override public void onTextChanged(CharSequence s,int start, int before, int count) {
465 @Override public void onClick(DialogInterface dialog, int button) {
466 mUnlockConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
469 @Override public void onDismiss(DialogInterface dialog) {
470 if (mUnlockConfirmed) {
471 mUnlockConfirmed = false;
472 mError.setVisibility(View.VISIBLE);
473 mKeyStore.unlock(mOldPassword.getText().toString());
474 int error = mKeyStore.getLastError();
475 if (error == KeyStore.NO_ERROR) {
476 mRetriesRemaining = -1;
477 Toast.makeText(CredentialStorage.this,
478 R.string.credentials_enabled,
479 Toast.LENGTH_SHORT).show();
480 // aha, now we are unlocked, switch to key guard.
481 // we'll end up back in onResume to install
483 } else if (error == KeyStore.UNINITIALIZED) {
484 mRetriesRemaining = -1;
485 Toast.makeText(CredentialStorage.this,
486 R.string.credentials_erased,
487 Toast.LENGTH_SHORT).show();
488 // we are reset, we can now set new password with key guard
489 handleUnlockOrInstall();
490 } else if (error >= KeyStore.WRONG_PASSWORD) {
491 // we need to try again
492 mRetriesRemaining = error - KeyStore.WRONG_PASSWORD + 1;
493 handleUnlockOrInstall();