Files
packages_apps_Settings/src/com/android/settings/security/CredentialStorage.java
Kevin Chyn b13bc50542 1/n: Make ChooseLockSettingsHelper into a builder
The multitude of slightly different launchConfirmationActivity(*)
methods are a big unsustainable pyramid. It's too difficult to
read, too difficult to track which clients are interested in which
parameters, and too difficult to add new parameters, since we need to

1) Read through all of them and find one that's the closest
2) Try not to affect other callers, so potentially add yet another
3) Modify the internal paths, which all basically call each other
   until it reaches the biggest launchConfirmationActivity which
   has ALL of the parameters

This change should have no behavioral change.

Note: CredentialStorage doesn't need returnCredentials anymore as of
      ag/6073449

Test: make -j56 RunSettingsRoboTests
Test: Manually traced code paths for each invocation. A few hidden
      dependencies (such as explicitly setting challenge=0 with
      hasChallenge=true) were found. Left them the way they were in
      case they were intended
Test: Enroll face, fingerprint
Test: Enable developer options
Test: Change to PIN, Pattern, Password, then back to PIN (so each
      type requests confirmation)
Test: adb shell am start -a android.app.action.CONFIRM_DEVICE_CREDENTIAL,
      authenticate
Test: adb shell am start -a android.app.action.CONFIRM_FRP_CREDENTIAL
      (shows confirm credential screen)
Fixes: 138453993

Change-Id: Ic82ef3c3ac2e14d624281921f2d816bcdacbd82b
2020-07-24 11:13:13 -07:00

380 lines
14 KiB
Java

/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.security;
import android.app.Activity;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.security.Credentials;
import android.security.IKeyChainService;
import android.security.KeyChain;
import android.security.KeyChain.KeyChainConnection;
import android.security.KeyStore;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.FragmentActivity;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.vpn2.VpnUtils;
/**
* CredentialStorage handles resetting and installing keys into KeyStore.
*/
public final class CredentialStorage extends FragmentActivity {
private static final String TAG = "CredentialStorage";
public static final String ACTION_INSTALL = "com.android.credentials.INSTALL";
public static final String ACTION_RESET = "com.android.credentials.RESET";
// This is the minimum acceptable password quality. If the current password quality is
// lower than this, keystore should not be activated.
public static final int MIN_PASSWORD_QUALITY = DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
private static final int CONFIRM_CLEAR_SYSTEM_CREDENTIAL_REQUEST = 1;
private final KeyStore mKeyStore = KeyStore.getInstance();
private LockPatternUtils mUtils;
/**
* When non-null, the bundle containing credentials to install.
*/
private Bundle mInstallBundle;
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
mUtils = new LockPatternUtils(this);
}
@Override
protected void onResume() {
super.onResume();
final Intent intent = getIntent();
final String action = intent.getAction();
final UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
if (!userManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_CREDENTIALS)) {
if (ACTION_RESET.equals(action)) {
new ResetDialog();
} else {
if (ACTION_INSTALL.equals(action) && checkCallerIsCertInstallerOrSelfInProfile()) {
mInstallBundle = intent.getExtras();
}
handleInstall();
}
} else {
finish();
}
}
/**
* Install credentials from mInstallBundle into Keystore.
*/
private void handleInstall() {
// something already decided we are done, do not proceed
if (isFinishing()) {
return;
}
if (installIfAvailable()) {
finish();
}
}
/**
* Install credentials if available, otherwise do nothing.
*
* @return true if the installation is done and the activity should be finished, false if
* an asynchronous task is pending and will finish the activity when it's done.
*/
private boolean installIfAvailable() {
if (mInstallBundle == null || mInstallBundle.isEmpty()) {
return true;
}
final Bundle bundle = mInstallBundle;
mInstallBundle = null;
final int uid = bundle.getInt(Credentials.EXTRA_INSTALL_AS_UID, KeyStore.UID_SELF);
if (uid != KeyStore.UID_SELF && !UserHandle.isSameUser(uid, Process.myUid())) {
final int dstUserId = UserHandle.getUserId(uid);
// Restrict install target to the wifi uid.
if (uid != Process.WIFI_UID) {
Log.e(TAG, "Failed to install credentials as uid " + uid + ": cross-user installs"
+ " may only target wifi uids");
return true;
}
final Intent installIntent = new Intent(ACTION_INSTALL)
.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT)
.putExtras(bundle);
startActivityAsUser(installIntent, new UserHandle(dstUserId));
return true;
}
String alias = bundle.getString(Credentials.EXTRA_USER_KEY_ALIAS, null);
if (TextUtils.isEmpty(alias)) {
Log.e(TAG, "Cannot install key without an alias");
return true;
}
final byte[] privateKeyData = bundle.getByteArray(Credentials.EXTRA_USER_PRIVATE_KEY_DATA);
final byte[] certData = bundle.getByteArray(Credentials.EXTRA_USER_CERTIFICATE_DATA);
final byte[] caListData = bundle.getByteArray(Credentials.EXTRA_CA_CERTIFICATES_DATA);
new InstallKeyInKeyChain(alias, privateKeyData, certData, caListData, uid).execute();
return false;
}
/**
* Prompt for reset confirmation, resetting on confirmation, finishing otherwise.
*/
private class ResetDialog
implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
private boolean mResetConfirmed;
private ResetDialog() {
final AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
.setTitle(android.R.string.dialog_alert_title)
.setMessage(R.string.credentials_reset_hint)
.setPositiveButton(android.R.string.ok, this)
.setNegativeButton(android.R.string.cancel, this)
.create();
dialog.setOnDismissListener(this);
dialog.show();
}
@Override
public void onClick(DialogInterface dialog, int button) {
mResetConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
}
@Override
public void onDismiss(DialogInterface dialog) {
if (!mResetConfirmed) {
finish();
return;
}
mResetConfirmed = false;
if (!mUtils.isSecure(UserHandle.myUserId())) {
// This task will call finish() in the end.
new ResetKeyStoreAndKeyChain().execute();
} else if (!confirmKeyGuard(CONFIRM_CLEAR_SYSTEM_CREDENTIAL_REQUEST)) {
Log.w(TAG, "Failed to launch credential confirmation for a secure user.");
finish();
}
// Confirmation result will be handled in onActivityResult if needed.
}
}
/**
* Background task to handle reset of both keystore and user installed CAs.
*/
private class ResetKeyStoreAndKeyChain extends AsyncTask<Void, Void, Boolean> {
@Override
protected Boolean doInBackground(Void... unused) {
// Clear all the users credentials could have been installed in for this user.
mUtils.resetKeyStore(UserHandle.myUserId());
try {
final KeyChainConnection keyChainConnection = KeyChain.bind(CredentialStorage.this);
try {
return keyChainConnection.getService().reset();
} catch (RemoteException e) {
return false;
} finally {
keyChainConnection.close();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
@Override
protected void onPostExecute(Boolean success) {
if (success) {
Toast.makeText(CredentialStorage.this,
R.string.credentials_erased, Toast.LENGTH_SHORT).show();
clearLegacyVpnIfEstablished();
} else {
Toast.makeText(CredentialStorage.this,
R.string.credentials_not_erased, Toast.LENGTH_SHORT).show();
}
finish();
}
}
private void clearLegacyVpnIfEstablished() {
final boolean isDone = VpnUtils.disconnectLegacyVpn(getApplicationContext());
if (isDone) {
Toast.makeText(CredentialStorage.this, R.string.vpn_disconnected,
Toast.LENGTH_SHORT).show();
}
}
/**
* Background task to install a certificate into KeyChain or the WiFi Keystore.
*/
private class InstallKeyInKeyChain extends AsyncTask<Void, Void, Boolean> {
final String mAlias;
private final byte[] mKeyData;
private final byte[] mCertData;
private final byte[] mCaListData;
private final int mUid;
InstallKeyInKeyChain(String alias, byte[] keyData, byte[] certData, byte[] caListData,
int uid) {
mAlias = alias;
mKeyData = keyData;
mCertData = certData;
mCaListData = caListData;
mUid = uid;
}
@Override
protected Boolean doInBackground(Void... unused) {
try (KeyChainConnection keyChainConnection = KeyChain.bind(CredentialStorage.this)) {
IKeyChainService service = keyChainConnection.getService();
if (!service.installKeyPair(mKeyData, mCertData, mCaListData, mAlias, mUid)) {
Log.w(TAG, String.format("Failed installing key %s", mAlias));
return false;
}
// If this is not a WiFi key, mark it as user-selectable, so that it can be
// selected by users from the Certificate Selection prompt.
if (mUid == Process.SYSTEM_UID || mUid == KeyStore.UID_SELF) {
service.setUserSelectable(mAlias, true);
}
return true;
} catch (RemoteException e) {
Log.w(TAG, String.format("Failed to install key %s to uid %d", mAlias, mUid), e);
return false;
} catch (InterruptedException e) {
Log.w(TAG, String.format("Interrupted while installing key %s", mAlias), e);
Thread.currentThread().interrupt();
return false;
}
}
@Override
protected void onPostExecute(Boolean result) {
CredentialStorage.this.onKeyInstalled(mAlias, mUid, result);
}
}
private void onKeyInstalled(String alias, int uid, boolean result) {
if (!result) {
Log.w(TAG, String.format("Error installing alias %s for uid %d", alias, uid));
finish();
return;
}
Log.i(TAG, String.format("Successfully installed alias %s to uid %d.",
alias, uid));
// Send the broadcast.
final Intent broadcast = new Intent(KeyChain.ACTION_KEYCHAIN_CHANGED);
sendBroadcast(broadcast);
setResult(RESULT_OK);
finish();
}
/**
* Check that the caller is either certinstaller or Settings running in a profile of this user.
*/
private boolean checkCallerIsCertInstallerOrSelfInProfile() {
if (TextUtils.equals("com.android.certinstaller", getCallingPackage())) {
// CertInstaller is allowed to install credentials if it has the same signature as
// Settings package.
return getPackageManager().checkSignatures(
getCallingPackage(), getPackageName()) == PackageManager.SIGNATURE_MATCH;
}
final int launchedFromUserId;
try {
final int launchedFromUid = android.app.ActivityManager.getService()
.getLaunchedFromUid(getActivityToken());
if (launchedFromUid == -1) {
Log.e(TAG, ACTION_INSTALL + " must be started with startActivityForResult");
return false;
}
if (!UserHandle.isSameApp(launchedFromUid, Process.myUid())) {
// Not the same app
return false;
}
launchedFromUserId = UserHandle.getUserId(launchedFromUid);
} catch (RemoteException re) {
// Error talking to ActivityManager, just give up
return false;
}
final UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
final UserInfo parentInfo = userManager.getProfileParent(launchedFromUserId);
// Caller is running in a profile of this user
return ((parentInfo != null) && (parentInfo.id == UserHandle.myUserId()));
}
/**
* Confirm existing key guard, returning password via onActivityResult.
*/
private boolean confirmKeyGuard(int requestCode) {
final Resources res = getResources();
final ChooseLockSettingsHelper.Builder builder =
new ChooseLockSettingsHelper.Builder(this);
return builder.setRequestCode(requestCode)
.setTitle(res.getText(R.string.credentials_title))
.show();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == CONFIRM_CLEAR_SYSTEM_CREDENTIAL_REQUEST) {
if (resultCode == Activity.RESULT_OK) {
new ResetKeyStoreAndKeyChain().execute();
return;
}
// failed confirmation, bail
finish();
}
}
}