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.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; 46 47 import com.android.internal.widget.LockPatternUtils; 48 import com.android.org.bouncycastle.asn1.ASN1InputStream; 49 import com.android.org.bouncycastle.asn1.pkcs.PrivateKeyInfo; 50 51 import sun.security.util.ObjectIdentifier; 52 import sun.security.x509.AlgorithmId; 53 54 import java.io.ByteArrayInputStream; 55 import java.io.IOException; 56 57 /** 58 * CredentialStorage handles KeyStore reset, unlock, and install. 59 * 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 63 * necessary. 64 * 65 * KeyStore: UNINITALIZED 66 * KeyGuard: OFF 67 * Action: set up key guard 68 * Notes: factory state 69 * 70 * KeyStore: UNINITALIZED 71 * KeyGuard: ON 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 75 * 76 * KeyStore: LOCKED 77 * KeyGuard: OFF/ON 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 82 * 83 * KeyStore: UNLOCKED 84 * KeyGuard: OFF 85 * Action: set up key guard 86 * Notes: ensure key guard, then proceed 87 * 88 * KeyStore: UNLOCKED 89 * keyguard: ON 90 * Action: normal unlock/install 91 * Notes: this is the common case 92 */ 93 public final class CredentialStorage extends Activity { 94 95 private static final String TAG = "CredentialStorage"; 96 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"; 100 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; 104 105 private static final int CONFIRM_KEY_GUARD_REQUEST = 1; 106 private static final int CONFIRM_CLEAR_SYSTEM_CREDENTIAL_REQUEST = 2; 107 108 private final KeyStore mKeyStore = KeyStore.getInstance(); 109 110 /** 111 * When non-null, the bundle containing credentials to install. 112 */ 113 private Bundle mInstallBundle; 114 115 /** 116 * After unsuccessful KeyStore.unlock, the number of unlock 117 * attempts remaining before the KeyStore will reset itself. 118 * 119 * Reset to -1 on successful unlock or reset. 120 */ 121 private int mRetriesRemaining = -1; 122 123 @Override onResume()124 protected void onResume() { 125 super.onResume(); 126 127 Intent intent = getIntent(); 128 String action = intent.getAction(); 129 UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE); 130 if (!userManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_CREDENTIALS)) { 131 if (ACTION_RESET.equals(action)) { 132 new ResetDialog(); 133 } else { 134 if (ACTION_INSTALL.equals(action) && checkCallerIsCertInstallerOrSelfInProfile()) { 135 mInstallBundle = intent.getExtras(); 136 } 137 // ACTION_UNLOCK also handled here in addition to ACTION_INSTALL 138 handleUnlockOrInstall(); 139 } 140 } else { 141 // Users can set a screen lock if there is none even if they can't modify the 142 // credentials store. 143 if (ACTION_UNLOCK.equals(action) && mKeyStore.state() == KeyStore.State.UNINITIALIZED) { 144 ensureKeyGuard(); 145 } else { 146 finish(); 147 } 148 } 149 } 150 151 /** 152 * Based on the current state of the KeyStore and key guard, try to 153 * make progress on unlocking or installing to the keystore. 154 */ handleUnlockOrInstall()155 private void handleUnlockOrInstall() { 156 // something already decided we are done, do not proceed 157 if (isFinishing()) { 158 return; 159 } 160 switch (mKeyStore.state()) { 161 case UNINITIALIZED: { 162 ensureKeyGuard(); 163 return; 164 } 165 case LOCKED: { 166 new UnlockDialog(); 167 return; 168 } 169 case UNLOCKED: { 170 if (!checkKeyGuardQuality()) { 171 new ConfigureKeyGuardDialog(); 172 return; 173 } 174 installIfAvailable(); 175 finish(); 176 return; 177 } 178 } 179 } 180 181 /** 182 * Make sure the user enters the key guard to set or change the 183 * keystore password. This can be used in UNINITIALIZED to set the 184 * keystore password or UNLOCKED to change the password (as is the 185 * case after unlocking with an old-style password). 186 */ ensureKeyGuard()187 private void ensureKeyGuard() { 188 if (!checkKeyGuardQuality()) { 189 // key guard not setup, doing so will initialize keystore 190 new ConfigureKeyGuardDialog(); 191 // will return to onResume after Activity 192 return; 193 } 194 // force key guard confirmation 195 if (confirmKeyGuard(CONFIRM_KEY_GUARD_REQUEST)) { 196 // will return password value via onActivityResult 197 return; 198 } 199 finish(); 200 } 201 202 /** 203 * Returns true if the currently set key guard matches our minimum quality requirements. 204 */ checkKeyGuardQuality()205 private boolean checkKeyGuardQuality() { 206 int credentialOwner = 207 UserManager.get(this).getCredentialOwnerProfile(UserHandle.myUserId()); 208 int quality = new LockPatternUtils(this).getActivePasswordQuality(credentialOwner); 209 return (quality >= MIN_PASSWORD_QUALITY); 210 } 211 isHardwareBackedKey(byte[] keyData)212 private boolean isHardwareBackedKey(byte[] keyData) { 213 try { 214 ASN1InputStream bIn = new ASN1InputStream(new ByteArrayInputStream(keyData)); 215 PrivateKeyInfo pki = PrivateKeyInfo.getInstance(bIn.readObject()); 216 String algOid = pki.getAlgorithmId().getAlgorithm().getId(); 217 String algName = new AlgorithmId(new ObjectIdentifier(algOid)).getName(); 218 219 return KeyChain.isBoundKeyAlgorithm(algName); 220 } catch (IOException e) { 221 Log.e(TAG, "Failed to parse key data"); 222 return false; 223 } 224 } 225 226 /** 227 * Install credentials if available, otherwise do nothing. 228 */ installIfAvailable()229 private void installIfAvailable() { 230 if (mInstallBundle == null || mInstallBundle.isEmpty()) { 231 return; 232 } 233 234 Bundle bundle = mInstallBundle; 235 mInstallBundle = null; 236 237 final int uid = bundle.getInt(Credentials.EXTRA_INSTALL_AS_UID, KeyStore.UID_SELF); 238 239 if (uid != KeyStore.UID_SELF && !UserHandle.isSameUser(uid, Process.myUid())) { 240 int dstUserId = UserHandle.getUserId(uid); 241 int myUserId = UserHandle.myUserId(); 242 243 // Restrict install target to the wifi uid. 244 if (uid != Process.WIFI_UID) { 245 Log.e(TAG, "Failed to install credentials as uid " + uid + ": cross-user installs" 246 + " may only target wifi uids"); 247 return; 248 } 249 250 Intent installIntent = new Intent(ACTION_INSTALL) 251 .setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT) 252 .putExtras(bundle); 253 startActivityAsUser(installIntent, new UserHandle(dstUserId)); 254 return; 255 } 256 257 if (bundle.containsKey(Credentials.EXTRA_USER_PRIVATE_KEY_NAME)) { 258 String key = bundle.getString(Credentials.EXTRA_USER_PRIVATE_KEY_NAME); 259 byte[] value = bundle.getByteArray(Credentials.EXTRA_USER_PRIVATE_KEY_DATA); 260 261 int flags = KeyStore.FLAG_ENCRYPTED; 262 if (uid == Process.WIFI_UID && isHardwareBackedKey(value)) { 263 // Hardware backed keystore is secure enough to allow for WIFI stack 264 // to enable access to secure networks without user intervention 265 Log.d(TAG, "Saving private key with FLAG_NONE for WIFI_UID"); 266 flags = KeyStore.FLAG_NONE; 267 } 268 269 if (!mKeyStore.importKey(key, value, uid, flags)) { 270 Log.e(TAG, "Failed to install " + key + " as uid " + uid); 271 return; 272 } 273 } 274 275 int flags = (uid == Process.WIFI_UID) ? KeyStore.FLAG_NONE : KeyStore.FLAG_ENCRYPTED; 276 277 if (bundle.containsKey(Credentials.EXTRA_USER_CERTIFICATE_NAME)) { 278 String certName = bundle.getString(Credentials.EXTRA_USER_CERTIFICATE_NAME); 279 byte[] certData = bundle.getByteArray(Credentials.EXTRA_USER_CERTIFICATE_DATA); 280 281 if (!mKeyStore.put(certName, certData, uid, flags)) { 282 Log.e(TAG, "Failed to install " + certName + " as uid " + uid); 283 return; 284 } 285 } 286 287 if (bundle.containsKey(Credentials.EXTRA_CA_CERTIFICATES_NAME)) { 288 String caListName = bundle.getString(Credentials.EXTRA_CA_CERTIFICATES_NAME); 289 byte[] caListData = bundle.getByteArray(Credentials.EXTRA_CA_CERTIFICATES_DATA); 290 291 if (!mKeyStore.put(caListName, caListData, uid, flags)) { 292 Log.e(TAG, "Failed to install " + caListName + " as uid " + uid); 293 return; 294 } 295 } 296 297 setResult(RESULT_OK); 298 } 299 300 /** 301 * Prompt for reset confirmation, resetting on confirmation, finishing otherwise. 302 */ 303 private class ResetDialog 304 implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener 305 { 306 private boolean mResetConfirmed; 307 ResetDialog()308 private ResetDialog() { 309 AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this) 310 .setTitle(android.R.string.dialog_alert_title) 311 .setMessage(R.string.credentials_reset_hint) 312 .setPositiveButton(android.R.string.ok, this) 313 .setNegativeButton(android.R.string.cancel, this) 314 .create(); 315 dialog.setOnDismissListener(this); 316 dialog.show(); 317 } 318 onClick(DialogInterface dialog, int button)319 @Override public void onClick(DialogInterface dialog, int button) { 320 mResetConfirmed = (button == DialogInterface.BUTTON_POSITIVE); 321 } 322 onDismiss(DialogInterface dialog)323 @Override public void onDismiss(DialogInterface dialog) { 324 if (mResetConfirmed) { 325 mResetConfirmed = false; 326 if (confirmKeyGuard(CONFIRM_CLEAR_SYSTEM_CREDENTIAL_REQUEST)) { 327 // will return password value via onActivityResult 328 return; 329 } 330 } 331 finish(); 332 } 333 } 334 335 /** 336 * Background task to handle reset of both keystore and user installed CAs. 337 */ 338 private class ResetKeyStoreAndKeyChain extends AsyncTask<Void, Void, Boolean> { 339 doInBackground(Void... unused)340 @Override protected Boolean doInBackground(Void... unused) { 341 342 // Clear all the users credentials could have been installed in for this user. 343 new LockPatternUtils(CredentialStorage.this).resetKeyStore(UserHandle.myUserId()); 344 345 try { 346 KeyChainConnection keyChainConnection = KeyChain.bind(CredentialStorage.this); 347 try { 348 return keyChainConnection.getService().reset(); 349 } catch (RemoteException e) { 350 return false; 351 } finally { 352 keyChainConnection.close(); 353 } 354 } catch (InterruptedException e) { 355 Thread.currentThread().interrupt(); 356 return false; 357 } 358 } 359 onPostExecute(Boolean success)360 @Override protected void onPostExecute(Boolean success) { 361 if (success) { 362 Toast.makeText(CredentialStorage.this, 363 R.string.credentials_erased, Toast.LENGTH_SHORT).show(); 364 } else { 365 Toast.makeText(CredentialStorage.this, 366 R.string.credentials_not_erased, Toast.LENGTH_SHORT).show(); 367 } 368 finish(); 369 } 370 } 371 372 /** 373 * Prompt for key guard configuration confirmation. 374 */ 375 private class ConfigureKeyGuardDialog 376 implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener 377 { 378 private boolean mConfigureConfirmed; 379 ConfigureKeyGuardDialog()380 private ConfigureKeyGuardDialog() { 381 AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this) 382 .setTitle(android.R.string.dialog_alert_title) 383 .setMessage(R.string.credentials_configure_lock_screen_hint) 384 .setPositiveButton(android.R.string.ok, this) 385 .setNegativeButton(android.R.string.cancel, this) 386 .create(); 387 dialog.setOnDismissListener(this); 388 dialog.show(); 389 } 390 onClick(DialogInterface dialog, int button)391 @Override public void onClick(DialogInterface dialog, int button) { 392 mConfigureConfirmed = (button == DialogInterface.BUTTON_POSITIVE); 393 } 394 onDismiss(DialogInterface dialog)395 @Override public void onDismiss(DialogInterface dialog) { 396 if (mConfigureConfirmed) { 397 mConfigureConfirmed = false; 398 Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD); 399 intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY, 400 MIN_PASSWORD_QUALITY); 401 startActivity(intent); 402 return; 403 } 404 finish(); 405 } 406 } 407 408 /** 409 * Check that the caller is either certinstaller or Settings running in a profile of this user. 410 */ checkCallerIsCertInstallerOrSelfInProfile()411 private boolean checkCallerIsCertInstallerOrSelfInProfile() { 412 if (TextUtils.equals("com.android.certinstaller", getCallingPackage())) { 413 // CertInstaller is allowed to install credentials if it has the same signature as 414 // Settings package. 415 return getPackageManager().checkSignatures( 416 getCallingPackage(), getPackageName()) == PackageManager.SIGNATURE_MATCH; 417 } 418 419 final int launchedFromUserId; 420 try { 421 int launchedFromUid = android.app.ActivityManagerNative.getDefault() 422 .getLaunchedFromUid(getActivityToken()); 423 if (launchedFromUid == -1) { 424 Log.e(TAG, ACTION_INSTALL + " must be started with startActivityForResult"); 425 return false; 426 } 427 if (!UserHandle.isSameApp(launchedFromUid, Process.myUid())) { 428 // Not the same app 429 return false; 430 } 431 launchedFromUserId = UserHandle.getUserId(launchedFromUid); 432 } catch (RemoteException re) { 433 // Error talking to ActivityManager, just give up 434 return false; 435 } 436 437 UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE); 438 UserInfo parentInfo = userManager.getProfileParent(launchedFromUserId); 439 if (parentInfo == null || parentInfo.id != UserHandle.myUserId()) { 440 // Caller is not running in a profile of this user 441 return false; 442 } 443 return true; 444 } 445 446 /** 447 * Confirm existing key guard, returning password via onActivityResult. 448 */ confirmKeyGuard(int requestCode)449 private boolean confirmKeyGuard(int requestCode) { 450 Resources res = getResources(); 451 boolean launched = new ChooseLockSettingsHelper(this) 452 .launchConfirmationActivity(requestCode, 453 res.getText(R.string.credentials_title), true); 454 return launched; 455 } 456 457 @Override onActivityResult(int requestCode, int resultCode, Intent data)458 public void onActivityResult(int requestCode, int resultCode, Intent data) { 459 super.onActivityResult(requestCode, resultCode, data); 460 461 /** 462 * Receive key guard password initiated by confirmKeyGuard. 463 */ 464 if (requestCode == CONFIRM_KEY_GUARD_REQUEST) { 465 if (resultCode == Activity.RESULT_OK) { 466 String password = data.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); 467 if (!TextUtils.isEmpty(password)) { 468 // success 469 mKeyStore.unlock(password); 470 // return to onResume 471 return; 472 } 473 } 474 // failed confirmation, bail 475 finish(); 476 } else if (requestCode == CONFIRM_CLEAR_SYSTEM_CREDENTIAL_REQUEST) { 477 if (resultCode == Activity.RESULT_OK) { 478 new ResetKeyStoreAndKeyChain().execute(); 479 return; 480 } 481 // failed confirmation, bail 482 finish(); 483 } 484 } 485 486 /** 487 * Prompt for unlock with old-style password. 488 * 489 * On successful unlock, ensure migration to key guard before continuing. 490 * On unsuccessful unlock, retry by calling handleUnlockOrInstall. 491 */ 492 private class UnlockDialog implements TextWatcher, 493 DialogInterface.OnClickListener, DialogInterface.OnDismissListener 494 { 495 private boolean mUnlockConfirmed; 496 497 private final Button mButton; 498 private final TextView mOldPassword; 499 private final TextView mError; 500 UnlockDialog()501 private UnlockDialog() { 502 View view = View.inflate(CredentialStorage.this, R.layout.credentials_dialog, null); 503 504 CharSequence text; 505 if (mRetriesRemaining == -1) { 506 text = getResources().getText(R.string.credentials_unlock_hint); 507 } else if (mRetriesRemaining > 3) { 508 text = getResources().getText(R.string.credentials_wrong_password); 509 } else if (mRetriesRemaining == 1) { 510 text = getResources().getText(R.string.credentials_reset_warning); 511 } else { 512 text = getString(R.string.credentials_reset_warning_plural, mRetriesRemaining); 513 } 514 515 ((TextView) view.findViewById(R.id.hint)).setText(text); 516 mOldPassword = (TextView) view.findViewById(R.id.old_password); 517 mOldPassword.setVisibility(View.VISIBLE); 518 mOldPassword.addTextChangedListener(this); 519 mError = (TextView) view.findViewById(R.id.error); 520 521 AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this) 522 .setView(view) 523 .setTitle(R.string.credentials_unlock) 524 .setPositiveButton(android.R.string.ok, this) 525 .setNegativeButton(android.R.string.cancel, this) 526 .create(); 527 dialog.setOnDismissListener(this); 528 dialog.show(); 529 mButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE); 530 mButton.setEnabled(false); 531 } 532 afterTextChanged(Editable editable)533 @Override public void afterTextChanged(Editable editable) { 534 mButton.setEnabled(mOldPassword == null || mOldPassword.getText().length() > 0); 535 } 536 beforeTextChanged(CharSequence s, int start, int count, int after)537 @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { 538 } 539 onTextChanged(CharSequence s,int start, int before, int count)540 @Override public void onTextChanged(CharSequence s,int start, int before, int count) { 541 } 542 onClick(DialogInterface dialog, int button)543 @Override public void onClick(DialogInterface dialog, int button) { 544 mUnlockConfirmed = (button == DialogInterface.BUTTON_POSITIVE); 545 } 546 onDismiss(DialogInterface dialog)547 @Override public void onDismiss(DialogInterface dialog) { 548 if (mUnlockConfirmed) { 549 mUnlockConfirmed = false; 550 mError.setVisibility(View.VISIBLE); 551 mKeyStore.unlock(mOldPassword.getText().toString()); 552 int error = mKeyStore.getLastError(); 553 if (error == KeyStore.NO_ERROR) { 554 mRetriesRemaining = -1; 555 Toast.makeText(CredentialStorage.this, 556 R.string.credentials_enabled, 557 Toast.LENGTH_SHORT).show(); 558 // aha, now we are unlocked, switch to key guard. 559 // we'll end up back in onResume to install 560 ensureKeyGuard(); 561 } else if (error == KeyStore.UNINITIALIZED) { 562 mRetriesRemaining = -1; 563 Toast.makeText(CredentialStorage.this, 564 R.string.credentials_erased, 565 Toast.LENGTH_SHORT).show(); 566 // we are reset, we can now set new password with key guard 567 handleUnlockOrInstall(); 568 } else if (error >= KeyStore.WRONG_PASSWORD) { 569 // we need to try again 570 mRetriesRemaining = error - KeyStore.WRONG_PASSWORD + 1; 571 handleUnlockOrInstall(); 572 } 573 return; 574 } 575 finish(); 576 } 577 } 578 } 579