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; 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)) { 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, KeyStore.UID_SELF); 130 131 if (uid != KeyStore.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 .setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT) 143 .putExtras(bundle); 144 startActivityAsUser(installIntent, new UserHandle(dstUserId)); 145 return true; 146 } 147 148 String alias = bundle.getString(Credentials.EXTRA_USER_KEY_ALIAS, null); 149 if (TextUtils.isEmpty(alias)) { 150 Log.e(TAG, "Cannot install key without an alias"); 151 return true; 152 } 153 154 final byte[] privateKeyData = bundle.getByteArray(Credentials.EXTRA_USER_PRIVATE_KEY_DATA); 155 final byte[] certData = bundle.getByteArray(Credentials.EXTRA_USER_CERTIFICATE_DATA); 156 final byte[] caListData = bundle.getByteArray(Credentials.EXTRA_CA_CERTIFICATES_DATA); 157 new InstallKeyInKeyChain(alias, privateKeyData, certData, caListData, uid).execute(); 158 159 return false; 160 } 161 162 /** 163 * Prompt for reset confirmation, resetting on confirmation, finishing otherwise. 164 */ 165 private class ResetDialog 166 implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener { 167 private boolean mResetConfirmed; 168 ResetDialog()169 private ResetDialog() { 170 final AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this) 171 .setTitle(android.R.string.dialog_alert_title) 172 .setMessage(R.string.credentials_reset_hint) 173 .setPositiveButton(android.R.string.ok, this) 174 .setNegativeButton(android.R.string.cancel, this) 175 .create(); 176 dialog.setOnDismissListener(this); 177 dialog.show(); 178 } 179 180 @Override onClick(DialogInterface dialog, int button)181 public void onClick(DialogInterface dialog, int button) { 182 mResetConfirmed = (button == DialogInterface.BUTTON_POSITIVE); 183 } 184 185 @Override onDismiss(DialogInterface dialog)186 public void onDismiss(DialogInterface dialog) { 187 if (!mResetConfirmed) { 188 finish(); 189 return; 190 } 191 192 mResetConfirmed = false; 193 if (!mUtils.isSecure(UserHandle.myUserId())) { 194 // This task will call finish() in the end. 195 new ResetKeyStoreAndKeyChain().execute(); 196 } else if (!confirmKeyGuard(CONFIRM_CLEAR_SYSTEM_CREDENTIAL_REQUEST)) { 197 Log.w(TAG, "Failed to launch credential confirmation for a secure user."); 198 finish(); 199 } 200 // Confirmation result will be handled in onActivityResult if needed. 201 } 202 } 203 204 /** 205 * Background task to handle reset of both keystore and user installed CAs. 206 */ 207 private class ResetKeyStoreAndKeyChain extends AsyncTask<Void, Void, Boolean> { 208 209 @Override doInBackground(Void... unused)210 protected Boolean doInBackground(Void... unused) { 211 212 // Clear all the users credentials could have been installed in for this user. 213 mUtils.resetKeyStore(UserHandle.myUserId()); 214 215 try { 216 final KeyChainConnection keyChainConnection = KeyChain.bind(CredentialStorage.this); 217 try { 218 return keyChainConnection.getService().reset(); 219 } catch (RemoteException e) { 220 return false; 221 } finally { 222 keyChainConnection.close(); 223 } 224 } catch (InterruptedException e) { 225 Thread.currentThread().interrupt(); 226 return false; 227 } 228 } 229 230 @Override onPostExecute(Boolean success)231 protected void onPostExecute(Boolean success) { 232 if (success) { 233 Toast.makeText(CredentialStorage.this, 234 R.string.credentials_erased, Toast.LENGTH_SHORT).show(); 235 clearLegacyVpnIfEstablished(); 236 } else { 237 Toast.makeText(CredentialStorage.this, 238 R.string.credentials_not_erased, Toast.LENGTH_SHORT).show(); 239 } 240 finish(); 241 } 242 } 243 clearLegacyVpnIfEstablished()244 private void clearLegacyVpnIfEstablished() { 245 final boolean isDone = VpnUtils.disconnectLegacyVpn(getApplicationContext()); 246 if (isDone) { 247 Toast.makeText(CredentialStorage.this, R.string.vpn_disconnected, 248 Toast.LENGTH_SHORT).show(); 249 } 250 } 251 252 /** 253 * Background task to install a certificate into KeyChain or the WiFi Keystore. 254 */ 255 private class InstallKeyInKeyChain extends AsyncTask<Void, Void, Boolean> { 256 final String mAlias; 257 private final byte[] mKeyData; 258 private final byte[] mCertData; 259 private final byte[] mCaListData; 260 private final int mUid; 261 InstallKeyInKeyChain(String alias, byte[] keyData, byte[] certData, byte[] caListData, int uid)262 InstallKeyInKeyChain(String alias, byte[] keyData, byte[] certData, byte[] caListData, 263 int uid) { 264 mAlias = alias; 265 mKeyData = keyData; 266 mCertData = certData; 267 mCaListData = caListData; 268 mUid = uid; 269 } 270 271 @Override doInBackground(Void... unused)272 protected Boolean doInBackground(Void... unused) { 273 try (KeyChainConnection keyChainConnection = KeyChain.bind(CredentialStorage.this)) { 274 IKeyChainService service = keyChainConnection.getService(); 275 if (!service.installKeyPair(mKeyData, mCertData, mCaListData, mAlias, mUid)) { 276 Log.w(TAG, String.format("Failed installing key %s", mAlias)); 277 return false; 278 } 279 280 // If this is not a WiFi key, mark it as user-selectable, so that it can be 281 // selected by users from the Certificate Selection prompt. 282 if (mUid == Process.SYSTEM_UID || mUid == KeyStore.UID_SELF) { 283 service.setUserSelectable(mAlias, true); 284 } 285 286 return true; 287 } catch (RemoteException e) { 288 Log.w(TAG, String.format("Failed to install key %s to uid %d", mAlias, mUid), e); 289 return false; 290 } catch (InterruptedException e) { 291 Log.w(TAG, String.format("Interrupted while installing key %s", mAlias), e); 292 Thread.currentThread().interrupt(); 293 return false; 294 } 295 } 296 297 @Override onPostExecute(Boolean result)298 protected void onPostExecute(Boolean result) { 299 CredentialStorage.this.onKeyInstalled(mAlias, mUid, result); 300 } 301 } 302 onKeyInstalled(String alias, int uid, boolean result)303 private void onKeyInstalled(String alias, int uid, boolean result) { 304 if (!result) { 305 Log.w(TAG, String.format("Error installing alias %s for uid %d", alias, uid)); 306 finish(); 307 return; 308 } 309 310 Log.i(TAG, String.format("Successfully installed alias %s to uid %d.", 311 alias, uid)); 312 313 // Send the broadcast. 314 final Intent broadcast = new Intent(KeyChain.ACTION_KEYCHAIN_CHANGED); 315 sendBroadcast(broadcast); 316 setResult(RESULT_OK); 317 318 finish(); 319 } 320 321 /** 322 * Check that the caller is either certinstaller or Settings running in a profile of this user. 323 */ checkCallerIsCertInstallerOrSelfInProfile()324 private boolean checkCallerIsCertInstallerOrSelfInProfile() { 325 if (TextUtils.equals("com.android.certinstaller", getCallingPackage())) { 326 // CertInstaller is allowed to install credentials if it has the same signature as 327 // Settings package. 328 return getPackageManager().checkSignatures( 329 getCallingPackage(), getPackageName()) == PackageManager.SIGNATURE_MATCH; 330 } 331 332 final int launchedFromUserId; 333 try { 334 final int launchedFromUid = android.app.ActivityManager.getService() 335 .getLaunchedFromUid(getActivityToken()); 336 if (launchedFromUid == -1) { 337 Log.e(TAG, ACTION_INSTALL + " must be started with startActivityForResult"); 338 return false; 339 } 340 if (!UserHandle.isSameApp(launchedFromUid, Process.myUid())) { 341 // Not the same app 342 return false; 343 } 344 launchedFromUserId = UserHandle.getUserId(launchedFromUid); 345 } catch (RemoteException re) { 346 // Error talking to ActivityManager, just give up 347 return false; 348 } 349 350 final UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE); 351 final UserInfo parentInfo = userManager.getProfileParent(launchedFromUserId); 352 // Caller is running in a profile of this user 353 return ((parentInfo != null) && (parentInfo.id == UserHandle.myUserId())); 354 } 355 356 /** 357 * Confirm existing key guard, returning password via onActivityResult. 358 */ confirmKeyGuard(int requestCode)359 private boolean confirmKeyGuard(int requestCode) { 360 final Resources res = getResources(); 361 final ChooseLockSettingsHelper.Builder builder = 362 new ChooseLockSettingsHelper.Builder(this); 363 return builder.setRequestCode(requestCode) 364 .setTitle(res.getText(R.string.credentials_title)) 365 .show(); 366 } 367 368 @Override onActivityResult(int requestCode, int resultCode, Intent data)369 public void onActivityResult(int requestCode, int resultCode, Intent data) { 370 super.onActivityResult(requestCode, resultCode, data); 371 if (requestCode == CONFIRM_CLEAR_SYSTEM_CREDENTIAL_REQUEST) { 372 if (resultCode == Activity.RESULT_OK) { 373 new ResetKeyStoreAndKeyChain().execute(); 374 return; 375 } 376 // failed confirmation, bail 377 finish(); 378 } 379 } 380 } 381