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