Since the class 'android.security.KeyStore' is going away, the UID_SELF constant is being moved to a more appropriate location in KeyProperties. Update code that references this constant to use the new location. No change in behavior; the value of the constant remains the same. Bug: 326508120 Test: build Flag: NONE mechanical refactoring with no behavior change Change-Id: Iaca5b20c37b7952df8f26a99b3f7391c5a7f01b6
394 lines
14 KiB
Java
394 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.KeyProperties;
|
|
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;
|
|
import com.android.settingslib.core.lifecycle.HideNonSystemOverlayMixin;
|
|
|
|
/**
|
|
* 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 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);
|
|
getLifecycle().addObserver(new HideNonSystemOverlayMixin(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) && checkCallerIsSelf()) {
|
|
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, KeyProperties.UID_SELF);
|
|
|
|
if (uid != KeyProperties.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 == KeyProperties.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 Settings.
|
|
*/
|
|
private boolean checkCallerIsSelf() {
|
|
try {
|
|
return Process.myUid() == android.app.ActivityManager.getService()
|
|
.getLaunchedFromUid(getActivityToken());
|
|
} catch (RemoteException re) {
|
|
// Error talking to ActivityManager, just give up
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
}
|
|
}
|
|
}
|