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.security; 18 19 import android.app.Activity; 20 import android.app.admin.DevicePolicyManager; 21 import android.content.Context; 22 import android.content.DialogInterface; 23 import android.content.Intent; 24 import android.content.pm.PackageManager; 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.Process; 30 import android.os.RemoteException; 31 import android.os.UserHandle; 32 import android.os.UserManager; 33 import android.security.Credentials; 34 import android.security.IKeyChainService; 35 import android.security.KeyChain; 36 import android.security.KeyChain.KeyChainConnection; 37 import android.security.keystore.KeyProperties; 38 import android.text.TextUtils; 39 import android.util.Log; 40 import android.widget.Toast; 41 42 import androidx.appcompat.app.AlertDialog; 43 import androidx.fragment.app.FragmentActivity; 44 45 import com.android.internal.widget.LockPatternUtils; 46 import com.android.settings.R; 47 import com.android.settings.password.ChooseLockSettingsHelper; 48 import com.android.settings.vpn2.VpnUtils; 49 import com.android.settingslib.core.lifecycle.HideNonSystemOverlayMixin; 50 51 /** 52 * CredentialStorage handles resetting and installing keys into KeyStore. 53 */ 54 public final class CredentialStorage extends FragmentActivity { 55 56 private static final String TAG = "CredentialStorage"; 57 58 public static final String ACTION_INSTALL = "com.android.credentials.INSTALL"; 59 public static final String ACTION_RESET = "com.android.credentials.RESET"; 60 61 // This is the minimum acceptable password quality. If the current password quality is 62 // lower than this, keystore should not be activated. 63 public static final int MIN_PASSWORD_QUALITY = DevicePolicyManager.PASSWORD_QUALITY_SOMETHING; 64 65 private static final int CONFIRM_CLEAR_SYSTEM_CREDENTIAL_REQUEST = 1; 66 67 private LockPatternUtils mUtils; 68 69 /** 70 * When non-null, the bundle containing credentials to install. 71 */ 72 private Bundle mInstallBundle; 73 74 @Override onCreate(Bundle savedState)75 protected void onCreate(Bundle savedState) { 76 super.onCreate(savedState); 77 mUtils = new LockPatternUtils(this); 78 getLifecycle().addObserver(new HideNonSystemOverlayMixin(this)); 79 } 80 81 @Override onResume()82 protected void onResume() { 83 super.onResume(); 84 85 final Intent intent = getIntent(); 86 final String action = intent.getAction(); 87 final UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE); 88 if (!userManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_CREDENTIALS)) { 89 if (ACTION_RESET.equals(action) && checkCallerIsSelf()) { 90 new ResetDialog(); 91 } else { 92 if (ACTION_INSTALL.equals(action) && checkCallerIsCertInstallerOrSelfInProfile()) { 93 mInstallBundle = intent.getExtras(); 94 } 95 handleInstall(); 96 } 97 } else { 98 finish(); 99 } 100 } 101 102 /** 103 * Install credentials from mInstallBundle into Keystore. 104 */ handleInstall()105 private void handleInstall() { 106 // something already decided we are done, do not proceed 107 if (isFinishing()) { 108 return; 109 } 110 if (installIfAvailable()) { 111 finish(); 112 } 113 } 114 115 /** 116 * Install credentials if available, otherwise do nothing. 117 * 118 * @return true if the installation is done and the activity should be finished, false if 119 * an asynchronous task is pending and will finish the activity when it's done. 120 */ installIfAvailable()121 private boolean installIfAvailable() { 122 if (mInstallBundle == null || mInstallBundle.isEmpty()) { 123 return true; 124 } 125 126 final Bundle bundle = mInstallBundle; 127 mInstallBundle = null; 128 129 final int uid = bundle.getInt(Credentials.EXTRA_INSTALL_AS_UID, KeyProperties.UID_SELF); 130 131 if (uid != KeyProperties.UID_SELF && !UserHandle.isSameUser(uid, Process.myUid())) { 132 final int dstUserId = UserHandle.getUserId(uid); 133 134 // Restrict install target to the wifi uid. 135 if (uid != Process.WIFI_UID) { 136 Log.e(TAG, "Failed to install credentials as uid " + uid + ": cross-user installs" 137 + " may only target wifi uids"); 138 return true; 139 } 140 141 final Intent installIntent = new Intent(ACTION_INSTALL) 142 .setPackage(getPackageName()) 143 .setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT) 144 .putExtras(bundle); 145 startActivityAsUser(installIntent, new UserHandle(dstUserId)); 146 return true; 147 } 148 149 String alias = bundle.getString(Credentials.EXTRA_USER_KEY_ALIAS, null); 150 if (TextUtils.isEmpty(alias)) { 151 Log.e(TAG, "Cannot install key without an alias"); 152 return true; 153 } 154 155 final byte[] privateKeyData = bundle.getByteArray(Credentials.EXTRA_USER_PRIVATE_KEY_DATA); 156 final byte[] certData = bundle.getByteArray(Credentials.EXTRA_USER_CERTIFICATE_DATA); 157 final byte[] caListData = bundle.getByteArray(Credentials.EXTRA_CA_CERTIFICATES_DATA); 158 new InstallKeyInKeyChain(alias, privateKeyData, certData, caListData, uid).execute(); 159 160 return false; 161 } 162 163 /** 164 * Prompt for reset confirmation, resetting on confirmation, finishing otherwise. 165 */ 166 private class ResetDialog 167 implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener { 168 private boolean mResetConfirmed; 169 ResetDialog()170 private ResetDialog() { 171 final AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this) 172 .setTitle(android.R.string.dialog_alert_title) 173 .setMessage(R.string.credentials_reset_hint) 174 .setPositiveButton(android.R.string.ok, this) 175 .setNegativeButton(android.R.string.cancel, this) 176 .create(); 177 dialog.setOnDismissListener(this); 178 dialog.show(); 179 } 180 181 @Override onClick(DialogInterface dialog, int button)182 public void onClick(DialogInterface dialog, int button) { 183 mResetConfirmed = (button == DialogInterface.BUTTON_POSITIVE); 184 } 185 186 @Override onDismiss(DialogInterface dialog)187 public void onDismiss(DialogInterface dialog) { 188 if (!mResetConfirmed) { 189 finish(); 190 return; 191 } 192 193 mResetConfirmed = false; 194 if (!mUtils.isSecure(UserHandle.myUserId())) { 195 // This task will call finish() in the end. 196 new ResetKeyStoreAndKeyChain().execute(); 197 } else if (!confirmKeyGuard(CONFIRM_CLEAR_SYSTEM_CREDENTIAL_REQUEST)) { 198 Log.w(TAG, "Failed to launch credential confirmation for a secure user."); 199 finish(); 200 } 201 // Confirmation result will be handled in onActivityResult if needed. 202 } 203 } 204 205 /** 206 * Background task to handle reset of both keystore and user installed CAs. 207 */ 208 private class ResetKeyStoreAndKeyChain extends AsyncTask<Void, Void, Boolean> { 209 210 @Override doInBackground(Void... unused)211 protected Boolean doInBackground(Void... unused) { 212 213 // Clear all the users credentials could have been installed in for this user. 214 mUtils.resetKeyStore(UserHandle.myUserId()); 215 216 try { 217 final KeyChainConnection keyChainConnection = KeyChain.bind(CredentialStorage.this); 218 try { 219 return keyChainConnection.getService().reset(); 220 } catch (RemoteException e) { 221 return false; 222 } finally { 223 keyChainConnection.close(); 224 } 225 } catch (InterruptedException e) { 226 Thread.currentThread().interrupt(); 227 return false; 228 } 229 } 230 231 @Override onPostExecute(Boolean success)232 protected void onPostExecute(Boolean success) { 233 if (success) { 234 Toast.makeText(CredentialStorage.this, 235 R.string.credentials_erased, Toast.LENGTH_SHORT).show(); 236 clearLegacyVpnIfEstablished(); 237 } else { 238 Toast.makeText(CredentialStorage.this, 239 R.string.credentials_not_erased, Toast.LENGTH_SHORT).show(); 240 } 241 finish(); 242 } 243 } 244 clearLegacyVpnIfEstablished()245 private void clearLegacyVpnIfEstablished() { 246 final boolean isDone = VpnUtils.disconnectLegacyVpn(getApplicationContext()); 247 if (isDone) { 248 Toast.makeText(CredentialStorage.this, R.string.vpn_disconnected, 249 Toast.LENGTH_SHORT).show(); 250 } 251 } 252 253 /** 254 * Background task to install a certificate into KeyChain or the WiFi Keystore. 255 */ 256 private class InstallKeyInKeyChain extends AsyncTask<Void, Void, Boolean> { 257 final String mAlias; 258 private final byte[] mKeyData; 259 private final byte[] mCertData; 260 private final byte[] mCaListData; 261 private final int mUid; 262 InstallKeyInKeyChain(String alias, byte[] keyData, byte[] certData, byte[] caListData, int uid)263 InstallKeyInKeyChain(String alias, byte[] keyData, byte[] certData, byte[] caListData, 264 int uid) { 265 mAlias = alias; 266 mKeyData = keyData; 267 mCertData = certData; 268 mCaListData = caListData; 269 mUid = uid; 270 } 271 272 @Override doInBackground(Void... unused)273 protected Boolean doInBackground(Void... unused) { 274 try (KeyChainConnection keyChainConnection = KeyChain.bind(CredentialStorage.this)) { 275 IKeyChainService service = keyChainConnection.getService(); 276 if (!service.installKeyPair(mKeyData, mCertData, mCaListData, mAlias, mUid)) { 277 Log.w(TAG, String.format("Failed installing key %s", mAlias)); 278 return false; 279 } 280 281 // If this is not a WiFi key, mark it as user-selectable, so that it can be 282 // selected by users from the Certificate Selection prompt. 283 if (mUid == Process.SYSTEM_UID || mUid == KeyProperties.UID_SELF) { 284 service.setUserSelectable(mAlias, true); 285 } 286 287 return true; 288 } catch (RemoteException e) { 289 Log.w(TAG, String.format("Failed to install key %s to uid %d", mAlias, mUid), e); 290 return false; 291 } catch (InterruptedException e) { 292 Log.w(TAG, String.format("Interrupted while installing key %s", mAlias), e); 293 Thread.currentThread().interrupt(); 294 return false; 295 } 296 } 297 298 @Override onPostExecute(Boolean result)299 protected void onPostExecute(Boolean result) { 300 CredentialStorage.this.onKeyInstalled(mAlias, mUid, result); 301 } 302 } 303 onKeyInstalled(String alias, int uid, boolean result)304 private void onKeyInstalled(String alias, int uid, boolean result) { 305 if (!result) { 306 Log.w(TAG, String.format("Error installing alias %s for uid %d", alias, uid)); 307 finish(); 308 return; 309 } 310 311 Log.i(TAG, String.format("Successfully installed alias %s to uid %d.", 312 alias, uid)); 313 314 // Send the broadcast. 315 final Intent broadcast = new Intent(KeyChain.ACTION_KEYCHAIN_CHANGED); 316 sendBroadcast(broadcast); 317 setResult(RESULT_OK); 318 319 finish(); 320 } 321 322 /** 323 * Check that the caller is Settings. 324 */ checkCallerIsSelf()325 private boolean checkCallerIsSelf() { 326 try { 327 return Process.myUid() == android.app.ActivityManager.getService() 328 .getLaunchedFromUid(getActivityToken()); 329 } catch (RemoteException re) { 330 // Error talking to ActivityManager, just give up 331 return false; 332 } 333 } 334 335 /** 336 * Check that the caller is either certinstaller or Settings running in a profile of this user. 337 */ checkCallerIsCertInstallerOrSelfInProfile()338 private boolean checkCallerIsCertInstallerOrSelfInProfile() { 339 if (TextUtils.equals("com.android.certinstaller", getCallingPackage())) { 340 // CertInstaller is allowed to install credentials if it has the same signature as 341 // Settings package. 342 return getPackageManager().checkSignatures( 343 getCallingPackage(), getPackageName()) == PackageManager.SIGNATURE_MATCH; 344 } 345 346 final int launchedFromUserId; 347 try { 348 final int launchedFromUid = android.app.ActivityManager.getService() 349 .getLaunchedFromUid(getActivityToken()); 350 if (launchedFromUid == -1) { 351 Log.e(TAG, ACTION_INSTALL + " must be started with startActivityForResult"); 352 return false; 353 } 354 if (!UserHandle.isSameApp(launchedFromUid, Process.myUid())) { 355 // Not the same app 356 return false; 357 } 358 launchedFromUserId = UserHandle.getUserId(launchedFromUid); 359 } catch (RemoteException re) { 360 // Error talking to ActivityManager, just give up 361 return false; 362 } 363 364 final UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE); 365 final UserInfo parentInfo = userManager.getProfileParent(launchedFromUserId); 366 // Caller is running in a profile of this user 367 return ((parentInfo != null) && (parentInfo.id == UserHandle.myUserId())); 368 } 369 370 /** 371 * Confirm existing key guard, returning password via onActivityResult. 372 */ confirmKeyGuard(int requestCode)373 private boolean confirmKeyGuard(int requestCode) { 374 final Resources res = getResources(); 375 final ChooseLockSettingsHelper.Builder builder = 376 new ChooseLockSettingsHelper.Builder(this); 377 return builder.setRequestCode(requestCode) 378 .setTitle(res.getText(R.string.credentials_title)) 379 .show(); 380 } 381 382 @Override onActivityResult(int requestCode, int resultCode, Intent data)383 public void onActivityResult(int requestCode, int resultCode, Intent data) { 384 super.onActivityResult(requestCode, resultCode, data); 385 if (requestCode == CONFIRM_CLEAR_SYSTEM_CREDENTIAL_REQUEST) { 386 if (resultCode == Activity.RESULT_OK) { 387 new ResetKeyStoreAndKeyChain().execute(); 388 return; 389 } 390 // failed confirmation, bail 391 finish(); 392 } 393 } 394 } 395