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.PackageManager;
26 import android.content.pm.UserInfo;
27 import android.content.res.Resources;
28 import android.os.AsyncTask;
29 import android.os.Bundle;
30 import android.os.Process;
31 import android.os.RemoteException;
32 import android.os.UserHandle;
33 import android.os.UserManager;
34 import android.security.Credentials;
35 import android.security.KeyChain;
36 import android.security.KeyChain.KeyChainConnection;
37 import android.security.KeyStore;
38 import android.text.Editable;
39 import android.text.TextUtils;
40 import android.text.TextWatcher;
41 import android.util.Log;
42 import android.view.View;
43 import android.widget.Button;
44 import android.widget.TextView;
45 import android.widget.Toast;
47 import com.android.internal.widget.LockPatternUtils;
48 import com.android.org.bouncycastle.asn1.ASN1InputStream;
49 import com.android.org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
51 import sun.security.util.ObjectIdentifier;
52 import sun.security.x509.AlgorithmId;
54 import java.io.ByteArrayInputStream;
55 import java.io.IOException;
58 * CredentialStorage handles KeyStore reset, unlock, and install.
60 * CredentialStorage has a pretty convoluted state machine to migrate
61 * from the old style separate keystore password to a new key guard
62 * based password, as well as to deal with setting up the key guard if
65 * KeyStore: UNINITALIZED
67 * Action: set up key guard
68 * Notes: factory state
70 * KeyStore: UNINITALIZED
72 * Action: confirm key guard
73 * Notes: user had key guard but no keystore and upgraded from pre-ICS
74 * OR user had key guard and pre-ICS keystore password which was then reset
78 * Action: old unlock dialog
79 * Notes: assume old password, need to use it to unlock.
80 * if unlock, ensure key guard before install.
81 * if reset, treat as UNINITALIZED/OFF
85 * Action: set up key guard
86 * Notes: ensure key guard, then proceed
90 * Action: normal unlock/install
91 * Notes: this is the common case
93 public final class CredentialStorage extends Activity {
95 private static final String TAG = "CredentialStorage";
97 public static final String ACTION_UNLOCK = "com.android.credentials.UNLOCK";
98 public static final String ACTION_INSTALL = "com.android.credentials.INSTALL";
99 public static final String ACTION_RESET = "com.android.credentials.RESET";
101 // This is the minimum acceptable password quality. If the current password quality is
102 // lower than this, keystore should not be activated.
103 static final int MIN_PASSWORD_QUALITY = DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
105 private static final int CONFIRM_KEY_GUARD_REQUEST = 1;
107 private final KeyStore mKeyStore = KeyStore.getInstance();
110 * The UIDs that are used for system credential storage in keystore.
112 private static final int[] SYSTEM_CREDENTIAL_UIDS = {Process.WIFI_UID, Process.VPN_UID,
113 Process.ROOT_UID, Process.SYSTEM_UID};
116 * When non-null, the bundle containing credentials to install.
118 private Bundle mInstallBundle;
121 * After unsuccessful KeyStore.unlock, the number of unlock
122 * attempts remaining before the KeyStore will reset itself.
124 * Reset to -1 on successful unlock or reset.
126 private int mRetriesRemaining = -1;
129 protected void onResume() {
132 Intent intent = getIntent();
133 String action = intent.getAction();
134 UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
135 if (!userManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_CREDENTIALS)) {
136 if (ACTION_RESET.equals(action)) {
139 if (ACTION_INSTALL.equals(action) && checkCallerIsCertInstallerOrSelfInProfile()) {
140 mInstallBundle = intent.getExtras();
142 // ACTION_UNLOCK also handled here in addition to ACTION_INSTALL
143 handleUnlockOrInstall();
146 // Users can set a screen lock if there is none even if they can't modify the
147 // credentials store.
148 if (ACTION_UNLOCK.equals(action) && mKeyStore.state() == KeyStore.State.UNINITIALIZED) {
157 * Based on the current state of the KeyStore and key guard, try to
158 * make progress on unlocking or installing to the keystore.
160 private void handleUnlockOrInstall() {
161 // something already decided we are done, do not proceed
165 switch (mKeyStore.state()) {
166 case UNINITIALIZED: {
175 if (!checkKeyGuardQuality()) {
176 new ConfigureKeyGuardDialog();
179 installIfAvailable();
187 * Make sure the user enters the key guard to set or change the
188 * keystore password. This can be used in UNINITIALIZED to set the
189 * keystore password or UNLOCKED to change the password (as is the
190 * case after unlocking with an old-style password).
192 private void ensureKeyGuard() {
193 if (!checkKeyGuardQuality()) {
194 // key guard not setup, doing so will initialize keystore
195 new ConfigureKeyGuardDialog();
196 // will return to onResume after Activity
199 // force key guard confirmation
200 if (confirmKeyGuard()) {
201 // will return password value via onActivityResult
208 * Returns true if the currently set key guard matches our minimum quality requirements.
210 private boolean checkKeyGuardQuality() {
211 UserInfo parent = UserManager.get(this).getProfileParent(UserHandle.myUserId());
212 int quality = new LockPatternUtils(this).getActivePasswordQuality(
213 parent != null ? parent.id : UserHandle.myUserId());
214 return (quality >= MIN_PASSWORD_QUALITY);
217 private boolean isHardwareBackedKey(byte[] keyData) {
219 ASN1InputStream bIn = new ASN1InputStream(new ByteArrayInputStream(keyData));
220 PrivateKeyInfo pki = PrivateKeyInfo.getInstance(bIn.readObject());
221 String algOid = pki.getAlgorithmId().getAlgorithm().getId();
222 String algName = new AlgorithmId(new ObjectIdentifier(algOid)).getName();
224 return KeyChain.isBoundKeyAlgorithm(algName);
225 } catch (IOException e) {
226 Log.e(TAG, "Failed to parse key data");
232 * Install credentials if available, otherwise do nothing.
234 private void installIfAvailable() {
235 if (mInstallBundle == null || mInstallBundle.isEmpty()) {
239 Bundle bundle = mInstallBundle;
240 mInstallBundle = null;
242 final int uid = bundle.getInt(Credentials.EXTRA_INSTALL_AS_UID, KeyStore.UID_SELF);
244 if (uid != KeyStore.UID_SELF && !UserHandle.isSameUser(uid, Process.myUid())) {
245 int dstUserId = UserHandle.getUserId(uid);
246 int myUserId = UserHandle.myUserId();
248 // Restrict install target to the wifi uid.
249 if (uid != Process.WIFI_UID) {
250 Log.e(TAG, "Failed to install credentials as uid " + uid + ": cross-user installs"
251 + " may only target wifi uids");
255 Intent installIntent = new Intent(ACTION_INSTALL)
256 .setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT)
258 startActivityAsUser(installIntent, new UserHandle(dstUserId));
262 if (bundle.containsKey(Credentials.EXTRA_USER_PRIVATE_KEY_NAME)) {
263 String key = bundle.getString(Credentials.EXTRA_USER_PRIVATE_KEY_NAME);
264 byte[] value = bundle.getByteArray(Credentials.EXTRA_USER_PRIVATE_KEY_DATA);
266 int flags = KeyStore.FLAG_ENCRYPTED;
267 if (uid == Process.WIFI_UID && isHardwareBackedKey(value)) {
268 // Hardware backed keystore is secure enough to allow for WIFI stack
269 // to enable access to secure networks without user intervention
270 Log.d(TAG, "Saving private key with FLAG_NONE for WIFI_UID");
271 flags = KeyStore.FLAG_NONE;
274 if (!mKeyStore.importKey(key, value, uid, flags)) {
275 Log.e(TAG, "Failed to install " + key + " as uid " + uid);
280 int flags = (uid == Process.WIFI_UID) ? KeyStore.FLAG_NONE : KeyStore.FLAG_ENCRYPTED;
282 if (bundle.containsKey(Credentials.EXTRA_USER_CERTIFICATE_NAME)) {
283 String certName = bundle.getString(Credentials.EXTRA_USER_CERTIFICATE_NAME);
284 byte[] certData = bundle.getByteArray(Credentials.EXTRA_USER_CERTIFICATE_DATA);
286 if (!mKeyStore.put(certName, certData, uid, flags)) {
287 Log.e(TAG, "Failed to install " + certName + " as uid " + uid);
292 if (bundle.containsKey(Credentials.EXTRA_CA_CERTIFICATES_NAME)) {
293 String caListName = bundle.getString(Credentials.EXTRA_CA_CERTIFICATES_NAME);
294 byte[] caListData = bundle.getByteArray(Credentials.EXTRA_CA_CERTIFICATES_DATA);
296 if (!mKeyStore.put(caListName, caListData, uid, flags)) {
297 Log.e(TAG, "Failed to install " + caListName + " as uid " + uid);
302 setResult(RESULT_OK);
306 * Prompt for reset confirmation, resetting on confirmation, finishing otherwise.
308 private class ResetDialog
309 implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener
311 private boolean mResetConfirmed;
313 private ResetDialog() {
314 AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
315 .setTitle(android.R.string.dialog_alert_title)
316 .setMessage(R.string.credentials_reset_hint)
317 .setPositiveButton(android.R.string.ok, this)
318 .setNegativeButton(android.R.string.cancel, this)
320 dialog.setOnDismissListener(this);
324 @Override public void onClick(DialogInterface dialog, int button) {
325 mResetConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
328 @Override public void onDismiss(DialogInterface dialog) {
329 if (mResetConfirmed) {
330 mResetConfirmed = false;
331 new ResetKeyStoreAndKeyChain().execute();
339 * Background task to handle reset of both keystore and user installed CAs.
341 private class ResetKeyStoreAndKeyChain extends AsyncTask<Void, Void, Boolean> {
343 @Override protected Boolean doInBackground(Void... unused) {
345 // Clear all the users credentials could have been installed in for this user.
346 final UserManager um = (UserManager) getSystemService(USER_SERVICE);
347 for (int userId : um.getProfileIdsWithDisabled(UserHandle.myUserId())) {
348 for (int uid : SYSTEM_CREDENTIAL_UIDS) {
349 mKeyStore.clearUid(UserHandle.getUid(userId, uid));
354 KeyChainConnection keyChainConnection = KeyChain.bind(CredentialStorage.this);
356 return keyChainConnection.getService().reset();
357 } catch (RemoteException e) {
360 keyChainConnection.close();
362 } catch (InterruptedException e) {
363 Thread.currentThread().interrupt();
368 @Override protected void onPostExecute(Boolean success) {
370 Toast.makeText(CredentialStorage.this,
371 R.string.credentials_erased, Toast.LENGTH_SHORT).show();
373 Toast.makeText(CredentialStorage.this,
374 R.string.credentials_not_erased, Toast.LENGTH_SHORT).show();
381 * Prompt for key guard configuration confirmation.
383 private class ConfigureKeyGuardDialog
384 implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener
386 private boolean mConfigureConfirmed;
388 private ConfigureKeyGuardDialog() {
389 AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
390 .setTitle(android.R.string.dialog_alert_title)
391 .setMessage(R.string.credentials_configure_lock_screen_hint)
392 .setPositiveButton(android.R.string.ok, this)
393 .setNegativeButton(android.R.string.cancel, this)
395 dialog.setOnDismissListener(this);
399 @Override public void onClick(DialogInterface dialog, int button) {
400 mConfigureConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
403 @Override public void onDismiss(DialogInterface dialog) {
404 if (mConfigureConfirmed) {
405 mConfigureConfirmed = false;
406 Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
407 intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY,
408 MIN_PASSWORD_QUALITY);
409 startActivity(intent);
417 * Check that the caller is either certinstaller or Settings running in a profile of this user.
419 private boolean checkCallerIsCertInstallerOrSelfInProfile() {
420 if (TextUtils.equals("com.android.certinstaller", getCallingPackage())) {
421 // CertInstaller is allowed to install credentials if it has the same signature as
423 return getPackageManager().checkSignatures(
424 getCallingPackage(), getPackageName()) == PackageManager.SIGNATURE_MATCH;
427 final int launchedFromUserId;
429 int launchedFromUid = android.app.ActivityManagerNative.getDefault()
430 .getLaunchedFromUid(getActivityToken());
431 if (launchedFromUid == -1) {
432 Log.e(TAG, ACTION_INSTALL + " must be started with startActivityForResult");
435 if (!UserHandle.isSameApp(launchedFromUid, Process.myUid())) {
439 launchedFromUserId = UserHandle.getUserId(launchedFromUid);
440 } catch (RemoteException re) {
441 // Error talking to ActivityManager, just give up
445 UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
446 UserInfo parentInfo = userManager.getProfileParent(launchedFromUserId);
447 if (parentInfo == null || parentInfo.id != UserHandle.myUserId()) {
448 // Caller is not running in a profile of this user
455 * Confirm existing key guard, returning password via onActivityResult.
457 private boolean confirmKeyGuard() {
458 Resources res = getResources();
459 boolean launched = new ChooseLockSettingsHelper(this)
460 .launchConfirmationActivity(CONFIRM_KEY_GUARD_REQUEST,
461 res.getText(R.string.credentials_title), true);
466 public void onActivityResult(int requestCode, int resultCode, Intent data) {
467 super.onActivityResult(requestCode, resultCode, data);
470 * Receive key guard password initiated by confirmKeyGuard.
472 if (requestCode == CONFIRM_KEY_GUARD_REQUEST) {
473 if (resultCode == Activity.RESULT_OK) {
474 String password = data.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
475 if (!TextUtils.isEmpty(password)) {
477 mKeyStore.unlock(password);
478 // return to onResume
482 // failed confirmation, bail
488 * Prompt for unlock with old-style password.
490 * On successful unlock, ensure migration to key guard before continuing.
491 * On unsuccessful unlock, retry by calling handleUnlockOrInstall.
493 private class UnlockDialog implements TextWatcher,
494 DialogInterface.OnClickListener, DialogInterface.OnDismissListener
496 private boolean mUnlockConfirmed;
498 private final Button mButton;
499 private final TextView mOldPassword;
500 private final TextView mError;
502 private UnlockDialog() {
503 View view = View.inflate(CredentialStorage.this, R.layout.credentials_dialog, null);
506 if (mRetriesRemaining == -1) {
507 text = getResources().getText(R.string.credentials_unlock_hint);
508 } else if (mRetriesRemaining > 3) {
509 text = getResources().getText(R.string.credentials_wrong_password);
510 } else if (mRetriesRemaining == 1) {
511 text = getResources().getText(R.string.credentials_reset_warning);
513 text = getString(R.string.credentials_reset_warning_plural, mRetriesRemaining);
516 ((TextView) view.findViewById(R.id.hint)).setText(text);
517 mOldPassword = (TextView) view.findViewById(R.id.old_password);
518 mOldPassword.setVisibility(View.VISIBLE);
519 mOldPassword.addTextChangedListener(this);
520 mError = (TextView) view.findViewById(R.id.error);
522 AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
524 .setTitle(R.string.credentials_unlock)
525 .setPositiveButton(android.R.string.ok, this)
526 .setNegativeButton(android.R.string.cancel, this)
528 dialog.setOnDismissListener(this);
530 mButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
531 mButton.setEnabled(false);
534 @Override public void afterTextChanged(Editable editable) {
535 mButton.setEnabled(mOldPassword == null || mOldPassword.getText().length() > 0);
538 @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {
541 @Override public void onTextChanged(CharSequence s,int start, int before, int count) {
544 @Override public void onClick(DialogInterface dialog, int button) {
545 mUnlockConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
548 @Override public void onDismiss(DialogInterface dialog) {
549 if (mUnlockConfirmed) {
550 mUnlockConfirmed = false;
551 mError.setVisibility(View.VISIBLE);
552 mKeyStore.unlock(mOldPassword.getText().toString());
553 int error = mKeyStore.getLastError();
554 if (error == KeyStore.NO_ERROR) {
555 mRetriesRemaining = -1;
556 Toast.makeText(CredentialStorage.this,
557 R.string.credentials_enabled,
558 Toast.LENGTH_SHORT).show();
559 // aha, now we are unlocked, switch to key guard.
560 // we'll end up back in onResume to install
562 } else if (error == KeyStore.UNINITIALIZED) {
563 mRetriesRemaining = -1;
564 Toast.makeText(CredentialStorage.this,
565 R.string.credentials_erased,
566 Toast.LENGTH_SHORT).show();
567 // we are reset, we can now set new password with key guard
568 handleUnlockOrInstall();
569 } else if (error >= KeyStore.WRONG_PASSWORD) {
570 // we need to try again
571 mRetriesRemaining = error - KeyStore.WRONG_PASSWORD + 1;
572 handleUnlockOrInstall();