summaryrefslogtreecommitdiffstats
path: root/src/com/android/settings/security
diff options
context:
space:
mode:
authorEran Messeri <eranm@google.com>2019-01-25 12:27:38 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2019-01-25 12:27:38 +0000
commit9b6005e51ec1a28dd233e4b03a583dfbb057e137 (patch)
tree14006b16f4786f2fc0576dd824813853b48efd5f /src/com/android/settings/security
parentb764fac7c127e827dce42a8170661fb1089fcda7 (diff)
parent1e9bd27e2bb00c36ccb6ee9f7af68da2394679ce (diff)
downloadpackages_apps_Settings-9b6005e51ec1a28dd233e4b03a583dfbb057e137.tar.gz
packages_apps_Settings-9b6005e51ec1a28dd233e4b03a583dfbb057e137.tar.bz2
packages_apps_Settings-9b6005e51ec1a28dd233e4b03a583dfbb057e137.zip
Merge "Further Credentials-related clean-up"
Diffstat (limited to 'src/com/android/settings/security')
-rw-r--r--src/com/android/settings/security/ConfigureKeyGuardDialog.java86
-rw-r--r--src/com/android/settings/security/CredentialStorage.java395
2 files changed, 395 insertions, 86 deletions
diff --git a/src/com/android/settings/security/ConfigureKeyGuardDialog.java b/src/com/android/settings/security/ConfigureKeyGuardDialog.java
deleted file mode 100644
index fff106c279..0000000000
--- a/src/com/android/settings/security/ConfigureKeyGuardDialog.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2017 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.Dialog;
-import android.app.admin.DevicePolicyManager;
-import android.app.settings.SettingsEnums;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.os.Bundle;
-
-import androidx.annotation.VisibleForTesting;
-import androidx.appcompat.app.AlertDialog;
-
-import com.android.settings.CredentialStorage;
-import com.android.settings.R;
-import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
-import com.android.settings.password.ChooseLockGeneric;
-
-/**
- * Prompt for key guard configuration confirmation.
- */
-public class ConfigureKeyGuardDialog extends InstrumentedDialogFragment
- implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
-
- public static final String TAG = "ConfigureKeyGuardDialog";
-
- private boolean mConfigureConfirmed;
-
- @Override
- public int getMetricsCategory() {
- return SettingsEnums.CONFIGURE_KEYGUARD_DIALOG;
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- return new AlertDialog.Builder(getActivity())
- .setTitle(android.R.string.dialog_alert_title)
- .setMessage(R.string.credentials_configure_lock_screen_hint)
- .setPositiveButton(R.string.credentials_configure_lock_screen_button, this)
- .setNegativeButton(android.R.string.cancel, this)
- .create();
- }
-
- @Override
- public void onClick(DialogInterface dialog, int button) {
- mConfigureConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
- }
-
- @Override
- public void onDismiss(DialogInterface dialog) {
- if (mConfigureConfirmed) {
- mConfigureConfirmed = false;
- startPasswordSetup();
- return;
- } else {
- final Activity activity = getActivity();
- if (activity != null) {
- activity.finish();
- }
- }
- }
-
- @VisibleForTesting
- void startPasswordSetup() {
- Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
- intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY,
- CredentialStorage.MIN_PASSWORD_QUALITY);
- startActivity(intent);
- }
-}
diff --git a/src/com/android/settings/security/CredentialStorage.java b/src/com/android/settings/security/CredentialStorage.java
new file mode 100644
index 0000000000..99937eee47
--- /dev/null
+++ b/src/com/android/settings/security/CredentialStorage.java
@@ -0,0 +1,395 @@
+/*
+ * 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.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.org.bouncycastle.asn1.ASN1InputStream;
+import com.android.org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import com.android.settings.R;
+import com.android.settings.password.ChooseLockSettingsHelper;
+import com.android.settings.vpn2.VpnUtils;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+import sun.security.util.ObjectIdentifier;
+import sun.security.x509.AlgorithmId;
+
+/**
+ * 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();
+ }
+ }
+
+ private boolean isHardwareBackedKey(byte[] keyData) {
+ try {
+ final ASN1InputStream bIn = new ASN1InputStream(new ByteArrayInputStream(keyData));
+ final PrivateKeyInfo pki = PrivateKeyInfo.getInstance(bIn.readObject());
+ final String algOid = pki.getPrivateKeyAlgorithm().getAlgorithm().getId();
+ final String algName = new AlgorithmId(new ObjectIdentifier(algOid)).getName();
+
+ return KeyChain.isBoundKeyAlgorithm(algName);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to parse key data");
+ return false;
+ }
+ }
+
+ /**
+ * 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;
+ }
+
+ boolean shouldFinish = true;
+ if (bundle.containsKey(Credentials.EXTRA_USER_PRIVATE_KEY_NAME)) {
+ final String key = bundle.getString(Credentials.EXTRA_USER_PRIVATE_KEY_NAME);
+ final byte[] value = bundle.getByteArray(Credentials.EXTRA_USER_PRIVATE_KEY_DATA);
+
+ if (!mKeyStore.importKey(key, value, uid, KeyStore.FLAG_NONE)) {
+ Log.e(TAG, "Failed to install " + key + " as uid " + uid);
+ return true;
+ }
+ // The key was prepended USER_PRIVATE_KEY by the CredentialHelper. However,
+ // KeyChain internally uses the raw alias name and only prepends USER_PRIVATE_KEY
+ // to the key name when interfacing with KeyStore.
+ // This is generally a symptom of CredentialStorage and CredentialHelper relying
+ // on internal implementation details of KeyChain and imitating its functionality
+ // rather than delegating to KeyChain for the certificate installation.
+ if (uid == Process.SYSTEM_UID || uid == KeyStore.UID_SELF) {
+ new MarkKeyAsUserSelectable(
+ key.replaceFirst("^" + Credentials.USER_PRIVATE_KEY, "")).execute();
+ shouldFinish = false;
+ }
+ }
+
+ final int flags = KeyStore.FLAG_NONE;
+
+ if (bundle.containsKey(Credentials.EXTRA_USER_CERTIFICATE_NAME)) {
+ final String certName = bundle.getString(Credentials.EXTRA_USER_CERTIFICATE_NAME);
+ final byte[] certData = bundle.getByteArray(Credentials.EXTRA_USER_CERTIFICATE_DATA);
+
+ if (!mKeyStore.put(certName, certData, uid, flags)) {
+ Log.e(TAG, "Failed to install " + certName + " as uid " + uid);
+ return shouldFinish;
+ }
+ }
+
+ if (bundle.containsKey(Credentials.EXTRA_CA_CERTIFICATES_NAME)) {
+ final String caListName = bundle.getString(Credentials.EXTRA_CA_CERTIFICATES_NAME);
+ final byte[] caListData = bundle.getByteArray(Credentials.EXTRA_CA_CERTIFICATES_DATA);
+
+ if (!mKeyStore.put(caListName, caListData, uid, flags)) {
+ Log.e(TAG, "Failed to install " + caListName + " as uid " + uid);
+ return shouldFinish;
+ }
+ }
+
+ // Send the broadcast.
+ final Intent broadcast = new Intent(KeyChain.ACTION_KEYCHAIN_CHANGED);
+ sendBroadcast(broadcast);
+
+ setResult(RESULT_OK);
+ return shouldFinish;
+ }
+
+ /**
+ * 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) {
+ mResetConfirmed = false;
+ if (confirmKeyGuard(CONFIRM_CLEAR_SYSTEM_CREDENTIAL_REQUEST)) {
+ // will return password value via onActivityResult
+ return;
+ }
+ }
+ finish();
+ }
+ }
+
+ /**
+ * 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 mark a given key alias as user-selectable, so that
+ * it can be selected by users from the Certificate Selection prompt.
+ */
+ private class MarkKeyAsUserSelectable extends AsyncTask<Void, Void, Boolean> {
+ final String mAlias;
+
+ MarkKeyAsUserSelectable(String alias) {
+ mAlias = alias;
+ }
+
+ @Override
+ protected Boolean doInBackground(Void... unused) {
+ try (KeyChainConnection keyChainConnection = KeyChain.bind(CredentialStorage.this)) {
+ keyChainConnection.getService().setUserSelectable(mAlias, true);
+ return true;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to mark key " + mAlias + " as user-selectable.");
+ return false;
+ } catch (InterruptedException e) {
+ Log.w(TAG, "Failed to mark key " + mAlias + " as user-selectable.");
+ Thread.currentThread().interrupt();
+ return false;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ Log.i(TAG, String.format("Marked alias %s as selectable, success? %s",
+ mAlias, result));
+ CredentialStorage.this.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();
+ return new ChooseLockSettingsHelper(this)
+ .launchConfirmationActivity(requestCode,
+ res.getText(R.string.credentials_title), true);
+ }
+
+ @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();
+ }
+ }
+}