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.pm.UserInfo;
26 import android.content.res.Resources;
27 import android.os.AsyncTask;
28 import android.os.Bundle;
29 import android.os.RemoteException;
30 import android.os.Process;
31 import android.os.UserHandle;
32 import android.os.UserManager;
33 import android.security.Credentials;
34 import android.security.KeyChain.KeyChainConnection;
35 import android.security.KeyChain;
36 import android.security.KeyStore;
37 import android.text.Editable;
38 import android.text.TextUtils;
39 import android.text.TextWatcher;
40 import android.util.Log;
41 import android.view.View;
42 import android.widget.Button;
43 import android.widget.TextView;
44 import android.widget.Toast;
46 import com.android.internal.widget.LockPatternUtils;
47 import com.android.org.bouncycastle.asn1.ASN1InputStream;
48 import com.android.org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
50 import org.apache.harmony.security.utils.AlgNameMapper;
52 import java.io.ByteArrayInputStream;
53 import java.io.IOException;
56 * CredentialStorage handles KeyStore reset, unlock, and install.
58 * CredentialStorage has a pretty convoluted state machine to migrate
59 * from the old style separate keystore password to a new key guard
60 * based password, as well as to deal with setting up the key guard if
63 * KeyStore: UNINITALIZED
65 * Action: set up key guard
66 * Notes: factory state
68 * KeyStore: UNINITALIZED
70 * Action: confirm key guard
71 * Notes: user had key guard but no keystore and upgraded from pre-ICS
72 * OR user had key guard and pre-ICS keystore password which was then reset
76 * Action: old unlock dialog
77 * Notes: assume old password, need to use it to unlock.
78 * if unlock, ensure key guard before install.
79 * if reset, treat as UNINITALIZED/OFF
83 * Action: set up key guard
84 * Notes: ensure key guard, then proceed
88 * Action: normal unlock/install
89 * Notes: this is the common case
91 public final class CredentialStorage extends Activity {
93 private static final String TAG = "CredentialStorage";
95 public static final String ACTION_UNLOCK = "com.android.credentials.UNLOCK";
96 public static final String ACTION_INSTALL = "com.android.credentials.INSTALL";
97 public static final String ACTION_RESET = "com.android.credentials.RESET";
99 // This is the minimum acceptable password quality. If the current password quality is
100 // lower than this, keystore should not be activated.
101 static final int MIN_PASSWORD_QUALITY = DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
103 private static final int CONFIRM_KEY_GUARD_REQUEST = 1;
105 private final KeyStore mKeyStore = KeyStore.getInstance();
108 * When non-null, the bundle containing credentials to install.
110 private Bundle mInstallBundle;
113 * After unsuccessful KeyStore.unlock, the number of unlock
114 * attempts remaining before the KeyStore will reset itself.
116 * Reset to -1 on successful unlock or reset.
118 private int mRetriesRemaining = -1;
121 protected void onResume() {
124 Intent intent = getIntent();
125 String action = intent.getAction();
126 UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
127 if (!userManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_CREDENTIALS)) {
128 if (ACTION_RESET.equals(action)) {
131 if (ACTION_INSTALL.equals(action) && checkCallerIsCertInstallerOrSelfInProfile()) {
132 mInstallBundle = intent.getExtras();
134 // ACTION_UNLOCK also handled here in addition to ACTION_INSTALL
135 handleUnlockOrInstall();
138 // Users can set a screen lock if there is none even if they can't modify the
139 // credentials store.
140 if (ACTION_UNLOCK.equals(action) && mKeyStore.state() == KeyStore.State.UNINITIALIZED) {
149 * Based on the current state of the KeyStore and key guard, try to
150 * make progress on unlocking or installing to the keystore.
152 private void handleUnlockOrInstall() {
153 // something already decided we are done, do not proceed
157 switch (mKeyStore.state()) {
158 case UNINITIALIZED: {
167 if (!checkKeyGuardQuality()) {
168 new ConfigureKeyGuardDialog();
171 installIfAvailable();
179 * Make sure the user enters the key guard to set or change the
180 * keystore password. This can be used in UNINITIALIZED to set the
181 * keystore password or UNLOCKED to change the password (as is the
182 * case after unlocking with an old-style password).
184 private void ensureKeyGuard() {
185 if (!checkKeyGuardQuality()) {
186 // key guard not setup, doing so will initialize keystore
187 new ConfigureKeyGuardDialog();
188 // will return to onResume after Activity
191 // force key guard confirmation
192 if (confirmKeyGuard()) {
193 // will return password value via onActivityResult
200 * Returns true if the currently set key guard matches our minimum quality requirements.
202 private boolean checkKeyGuardQuality() {
203 int quality = new LockPatternUtils(this).getActivePasswordQuality(
204 UserHandle.myUserId());
205 return (quality >= MIN_PASSWORD_QUALITY);
208 private boolean isHardwareBackedKey(byte[] keyData) {
210 ASN1InputStream bIn = new ASN1InputStream(new ByteArrayInputStream(keyData));
211 PrivateKeyInfo pki = PrivateKeyInfo.getInstance(bIn.readObject());
212 String algId = pki.getAlgorithmId().getAlgorithm().getId();
213 String algName = AlgNameMapper.map2AlgName(algId);
215 return KeyChain.isBoundKeyAlgorithm(algName);
216 } catch (IOException e) {
217 Log.e(TAG, "Failed to parse key data");
223 * Install credentials if available, otherwise do nothing.
225 private void installIfAvailable() {
226 if (mInstallBundle == null || mInstallBundle.isEmpty()) {
230 Bundle bundle = mInstallBundle;
231 mInstallBundle = null;
233 final int uid = bundle.getInt(Credentials.EXTRA_INSTALL_AS_UID, KeyStore.UID_SELF);
235 if (uid != KeyStore.UID_SELF && !UserHandle.isSameUser(uid, Process.myUid())) {
236 int dstUserId = UserHandle.getUserId(uid);
237 int myUserId = UserHandle.myUserId();
239 // Restrict install target to the wifi uid.
240 if (uid != Process.WIFI_UID) {
241 Log.e(TAG, "Failed to install credentials as uid " + uid + ": cross-user installs"
242 + " may only target wifi uids");
246 Intent installIntent = new Intent(ACTION_INSTALL)
247 .setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT)
249 startActivityAsUser(installIntent, new UserHandle(dstUserId));
253 if (bundle.containsKey(Credentials.EXTRA_USER_PRIVATE_KEY_NAME)) {
254 String key = bundle.getString(Credentials.EXTRA_USER_PRIVATE_KEY_NAME);
255 byte[] value = bundle.getByteArray(Credentials.EXTRA_USER_PRIVATE_KEY_DATA);
257 int flags = KeyStore.FLAG_ENCRYPTED;
258 if (uid == Process.WIFI_UID && isHardwareBackedKey(value)) {
259 // Hardware backed keystore is secure enough to allow for WIFI stack
260 // to enable access to secure networks without user intervention
261 Log.d(TAG, "Saving private key with FLAG_NONE for WIFI_UID");
262 flags = KeyStore.FLAG_NONE;
265 if (!mKeyStore.importKey(key, value, uid, flags)) {
266 Log.e(TAG, "Failed to install " + key + " as uid " + uid);
271 int flags = (uid == Process.WIFI_UID) ? KeyStore.FLAG_NONE : KeyStore.FLAG_ENCRYPTED;
273 if (bundle.containsKey(Credentials.EXTRA_USER_CERTIFICATE_NAME)) {
274 String certName = bundle.getString(Credentials.EXTRA_USER_CERTIFICATE_NAME);
275 byte[] certData = bundle.getByteArray(Credentials.EXTRA_USER_CERTIFICATE_DATA);
277 if (!mKeyStore.put(certName, certData, uid, flags)) {
278 Log.e(TAG, "Failed to install " + certName + " as uid " + uid);
283 if (bundle.containsKey(Credentials.EXTRA_CA_CERTIFICATES_NAME)) {
284 String caListName = bundle.getString(Credentials.EXTRA_CA_CERTIFICATES_NAME);
285 byte[] caListData = bundle.getByteArray(Credentials.EXTRA_CA_CERTIFICATES_DATA);
287 if (!mKeyStore.put(caListName, caListData, uid, flags)) {
288 Log.e(TAG, "Failed to install " + caListName + " as uid " + uid);
293 setResult(RESULT_OK);
297 * Prompt for reset confirmation, resetting on confirmation, finishing otherwise.
299 private class ResetDialog
300 implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener
302 private boolean mResetConfirmed;
304 private ResetDialog() {
305 AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
306 .setTitle(android.R.string.dialog_alert_title)
307 .setMessage(R.string.credentials_reset_hint)
308 .setPositiveButton(android.R.string.ok, this)
309 .setNegativeButton(android.R.string.cancel, this)
311 dialog.setOnDismissListener(this);
315 @Override public void onClick(DialogInterface dialog, int button) {
316 mResetConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
319 @Override public void onDismiss(DialogInterface dialog) {
320 if (mResetConfirmed) {
321 mResetConfirmed = false;
322 new ResetKeyStoreAndKeyChain().execute();
330 * Background task to handle reset of both keystore and user installed CAs.
332 private class ResetKeyStoreAndKeyChain extends AsyncTask<Void, Void, Boolean> {
334 @Override protected Boolean doInBackground(Void... unused) {
339 KeyChainConnection keyChainConnection = KeyChain.bind(CredentialStorage.this);
341 return keyChainConnection.getService().reset();
342 } catch (RemoteException e) {
345 keyChainConnection.close();
347 } catch (InterruptedException e) {
348 Thread.currentThread().interrupt();
353 @Override protected void onPostExecute(Boolean success) {
355 Toast.makeText(CredentialStorage.this,
356 R.string.credentials_erased, Toast.LENGTH_SHORT).show();
358 Toast.makeText(CredentialStorage.this,
359 R.string.credentials_not_erased, Toast.LENGTH_SHORT).show();
366 * Prompt for key guard configuration confirmation.
368 private class ConfigureKeyGuardDialog
369 implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener
371 private boolean mConfigureConfirmed;
373 private ConfigureKeyGuardDialog() {
374 AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
375 .setTitle(android.R.string.dialog_alert_title)
376 .setMessage(R.string.credentials_configure_lock_screen_hint)
377 .setPositiveButton(android.R.string.ok, this)
378 .setNegativeButton(android.R.string.cancel, this)
380 dialog.setOnDismissListener(this);
384 @Override public void onClick(DialogInterface dialog, int button) {
385 mConfigureConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
388 @Override public void onDismiss(DialogInterface dialog) {
389 if (mConfigureConfirmed) {
390 mConfigureConfirmed = false;
391 Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
392 intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY,
393 MIN_PASSWORD_QUALITY);
394 startActivity(intent);
402 * Check that the caller is either certinstaller or Settings running in a profile of this user.
404 private boolean checkCallerIsCertInstallerOrSelfInProfile() {
405 if (TextUtils.equals("com.android.certinstaller", getCallingPackage())) {
406 // CertInstaller is allowed to install credentials
410 final int launchedFromUserId;
412 int launchedFromUid = android.app.ActivityManagerNative.getDefault()
413 .getLaunchedFromUid(getActivityToken());
414 if (launchedFromUid == -1) {
415 Log.e(TAG, ACTION_INSTALL + " must be started with startActivityForResult");
418 if (!UserHandle.isSameApp(launchedFromUid, Process.myUid())) {
422 launchedFromUserId = UserHandle.getUserId(launchedFromUid);
423 } catch (RemoteException re) {
424 // Error talking to ActivityManager, just give up
428 UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
429 UserInfo parentInfo = userManager.getProfileParent(launchedFromUserId);
430 if (parentInfo == null || parentInfo.id != UserHandle.myUserId()) {
431 // Caller is not running in a profile of this user
438 * Confirm existing key guard, returning password via onActivityResult.
440 private boolean confirmKeyGuard() {
441 Resources res = getResources();
442 boolean launched = new ChooseLockSettingsHelper(this)
443 .launchConfirmationActivity(CONFIRM_KEY_GUARD_REQUEST,
444 res.getText(R.string.credentials_title), true);
449 public void onActivityResult(int requestCode, int resultCode, Intent data) {
450 super.onActivityResult(requestCode, resultCode, data);
453 * Receive key guard password initiated by confirmKeyGuard.
455 if (requestCode == CONFIRM_KEY_GUARD_REQUEST) {
456 if (resultCode == Activity.RESULT_OK) {
457 String password = data.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
458 if (!TextUtils.isEmpty(password)) {
460 mKeyStore.password(password);
461 // return to onResume
465 // failed confirmation, bail
471 * Prompt for unlock with old-style password.
473 * On successful unlock, ensure migration to key guard before continuing.
474 * On unsuccessful unlock, retry by calling handleUnlockOrInstall.
476 private class UnlockDialog implements TextWatcher,
477 DialogInterface.OnClickListener, DialogInterface.OnDismissListener
479 private boolean mUnlockConfirmed;
481 private final Button mButton;
482 private final TextView mOldPassword;
483 private final TextView mError;
485 private UnlockDialog() {
486 View view = View.inflate(CredentialStorage.this, R.layout.credentials_dialog, null);
489 if (mRetriesRemaining == -1) {
490 text = getResources().getText(R.string.credentials_unlock_hint);
491 } else if (mRetriesRemaining > 3) {
492 text = getResources().getText(R.string.credentials_wrong_password);
493 } else if (mRetriesRemaining == 1) {
494 text = getResources().getText(R.string.credentials_reset_warning);
496 text = getString(R.string.credentials_reset_warning_plural, mRetriesRemaining);
499 ((TextView) view.findViewById(R.id.hint)).setText(text);
500 mOldPassword = (TextView) view.findViewById(R.id.old_password);
501 mOldPassword.setVisibility(View.VISIBLE);
502 mOldPassword.addTextChangedListener(this);
503 mError = (TextView) view.findViewById(R.id.error);
505 AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
507 .setTitle(R.string.credentials_unlock)
508 .setPositiveButton(android.R.string.ok, this)
509 .setNegativeButton(android.R.string.cancel, this)
511 dialog.setOnDismissListener(this);
513 mButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
514 mButton.setEnabled(false);
517 @Override public void afterTextChanged(Editable editable) {
518 mButton.setEnabled(mOldPassword == null || mOldPassword.getText().length() > 0);
521 @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {
524 @Override public void onTextChanged(CharSequence s,int start, int before, int count) {
527 @Override public void onClick(DialogInterface dialog, int button) {
528 mUnlockConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
531 @Override public void onDismiss(DialogInterface dialog) {
532 if (mUnlockConfirmed) {
533 mUnlockConfirmed = false;
534 mError.setVisibility(View.VISIBLE);
535 mKeyStore.unlock(mOldPassword.getText().toString());
536 int error = mKeyStore.getLastError();
537 if (error == KeyStore.NO_ERROR) {
538 mRetriesRemaining = -1;
539 Toast.makeText(CredentialStorage.this,
540 R.string.credentials_enabled,
541 Toast.LENGTH_SHORT).show();
542 // aha, now we are unlocked, switch to key guard.
543 // we'll end up back in onResume to install
545 } else if (error == KeyStore.UNINITIALIZED) {
546 mRetriesRemaining = -1;
547 Toast.makeText(CredentialStorage.this,
548 R.string.credentials_erased,
549 Toast.LENGTH_SHORT).show();
550 // we are reset, we can now set new password with key guard
551 handleUnlockOrInstall();
552 } else if (error >= KeyStore.WRONG_PASSWORD) {
553 // we need to try again
554 mRetriesRemaining = error - KeyStore.WRONG_PASSWORD + 1;
555 handleUnlockOrInstall();