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