Include wifi certificates in settings
Deleting wifi certificates isn't supported yet since cascading the
removal back into wifi configs will need some easy way of enumerating
wifi configs first.
Bug: 29208062
Change-Id: I2d9d1ea7e0974701009bfa6ea162b8bc80806639
diff --git a/src/com/android/settings/UserCredentialsSettings.java b/src/com/android/settings/UserCredentialsSettings.java
index 2681da6..bbab0c5 100644
--- a/src/com/android/settings/UserCredentialsSettings.java
+++ b/src/com/android/settings/UserCredentialsSettings.java
@@ -16,6 +16,8 @@
package com.android.settings;
+import android.annotation.LayoutRes;
+import android.annotation.Nullable;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
@@ -26,6 +28,7 @@
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
@@ -35,6 +38,7 @@
import android.security.KeyChain.KeyChainConnection;
import android.security.KeyStore;
import android.util.Log;
+import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -49,7 +53,9 @@
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+import java.util.ArrayList;
import java.util.EnumSet;
+import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
@@ -59,7 +65,6 @@
public class UserCredentialsSettings extends OptionsMenuFragment implements OnItemClickListener {
private static final String TAG = "UserCredentialsSettings";
- private View mRootView;
private ListView mListView;
@Override
@@ -76,13 +81,13 @@
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
- mRootView = inflater.inflate(R.layout.user_credentials, parent, false);
+ final View rootView = inflater.inflate(R.layout.user_credentials, parent, false);
// Set up an OnItemClickListener for the credential list.
- mListView = (ListView) mRootView.findViewById(R.id.credential_list);
+ mListView = (ListView) rootView.findViewById(R.id.credential_list);
mListView.setOnItemClickListener(this);
- return mRootView;
+ return rootView;
}
@Override
@@ -122,15 +127,13 @@
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Credential item = (Credential) getArguments().getParcelable(ARG_CREDENTIAL);
+
View root = getActivity().getLayoutInflater()
.inflate(R.layout.user_credential_dialog, null);
ViewGroup infoContainer = (ViewGroup) root.findViewById(R.id.credential_container);
- View view = new CredentialAdapter(getActivity(), R.layout.user_credential,
- new Credential[] {item}).getView(0, null, null);
- infoContainer.addView(view);
-
- UserManager userManager
- = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
+ View contentView = getCredentialView(item, R.layout.user_credential, null,
+ infoContainer, /* expanded */ true);
+ infoContainer.addView(contentView);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
.setView(root)
@@ -148,49 +151,74 @@
RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(),
admin);
} else {
- new RemoveCredentialsTask(getTargetFragment()).execute(item.alias);
+ new RemoveCredentialsTask(getContext(), getTargetFragment())
+ .execute(item);
}
dialog.dismiss();
}
};
- builder.setNegativeButton(R.string.trusted_credentials_remove_label, listener);
+ if (item.isSystem()) {
+ // TODO: a safe means of clearing wifi certificates. Configs refer to aliases
+ // directly so deleting certs will break dependent access points.
+ builder.setNegativeButton(R.string.trusted_credentials_remove_label, listener);
+ }
}
return builder.create();
}
- private class RemoveCredentialsTask extends AsyncTask<String, Void, String[]> {
+ /**
+ * Deletes all certificates and keys under a given alias.
+ *
+ * If the {@link Credential} is for a system alias, all active grants to the alias will be
+ * removed using {@link KeyChain}.
+ */
+ private class RemoveCredentialsTask extends AsyncTask<Credential, Void, Credential[]> {
+ private Context context;
private Fragment targetFragment;
- public RemoveCredentialsTask(Fragment targetFragment) {
+ public RemoveCredentialsTask(Context context, Fragment targetFragment) {
+ this.context = context;
this.targetFragment = targetFragment;
}
@Override
- protected String[] doInBackground(String... aliases) {
- try {
- final KeyChainConnection conn = KeyChain.bind(getContext());
- try {
- IKeyChainService keyChain = conn.getService();
- for (String alias : aliases) {
- keyChain.removeKeyPair(alias);
- }
- } catch (RemoteException e) {
- Log.w(TAG, "Removing credentials", e);
- } finally {
- conn.close();
+ protected Credential[] doInBackground(Credential... credentials) {
+ for (final Credential credential : credentials) {
+ if (credential.isSystem()) {
+ removeGrantsAndDelete(credential);
+ continue;
}
- } catch (InterruptedException e) {
- Log.w(TAG, "Connecting to keychain", e);
+ throw new UnsupportedOperationException(
+ "Not implemented for wifi certificates. This should not be reachable.");
}
- return aliases;
+ return credentials;
+ }
+
+ private void removeGrantsAndDelete(final Credential credential) {
+ final KeyChainConnection conn;
+ try {
+ conn = KeyChain.bind(getContext());
+ } catch (InterruptedException e) {
+ Log.w(TAG, "Connecting to KeyChain", e);
+ return;
+ }
+
+ try {
+ IKeyChainService keyChain = conn.getService();
+ keyChain.removeKeyPair(credential.alias);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Removing credentials", e);
+ } finally {
+ conn.close();
+ }
}
@Override
- protected void onPostExecute(String... aliases) {
+ protected void onPostExecute(Credential... credentials) {
if (targetFragment instanceof UserCredentialsSettings && targetFragment.isAdded()) {
final UserCredentialsSettings target = (UserCredentialsSettings) targetFragment;
- for (final String alias : aliases) {
- target.announceRemoval(alias);
+ for (final Credential credential : credentials) {
+ target.announceRemoval(credential.alias);
}
target.refreshItems();
}
@@ -203,34 +231,53 @@
* The credentials are stored in a {@link CredentialAdapter} attached to the main
* {@link ListView} in the fragment.
*/
- private class AliasLoader extends AsyncTask<Void, Void, SortedMap<String, Credential>> {
+ private class AliasLoader extends AsyncTask<Void, Void, List<Credential>> {
+ /**
+ * @return a list of credentials ordered:
+ * <ol>
+ * <li>first by purpose;</li>
+ * <li>then by alias.</li>
+ * </ol>
+ */
@Override
- protected SortedMap<String, Credential> doInBackground(Void... params) {
- // Create a list of names for credential sets, ordered by name.
- SortedMap<String, Credential> credentials = new TreeMap<>();
- KeyStore keyStore = KeyStore.getInstance();
+ protected List<Credential> doInBackground(Void... params) {
+ final KeyStore keyStore = KeyStore.getInstance();
+
+ // Certificates can be installed into SYSTEM_UID or WIFI_UID through CertInstaller.
+ final int myUserId = UserHandle.myUserId();
+ final int systemUid = UserHandle.getUid(myUserId, Process.SYSTEM_UID);
+ final int wifiUid = UserHandle.getUid(myUserId, Process.WIFI_UID);
+
+ List<Credential> credentials = new ArrayList<>();
+ credentials.addAll(getCredentialsForUid(keyStore, systemUid).values());
+ credentials.addAll(getCredentialsForUid(keyStore, wifiUid).values());
+ return credentials;
+ }
+
+ private SortedMap<String, Credential> getCredentialsForUid(KeyStore keyStore, int uid) {
+ final SortedMap<String, Credential> aliasMap = new TreeMap<>();
for (final Credential.Type type : Credential.Type.values()) {
- for (final String alias : keyStore.list(type.prefix)) {
+ for (final String alias : keyStore.list(type.prefix, uid)) {
// Do not show work profile keys in user credentials
if (alias.startsWith(LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT) ||
alias.startsWith(LockPatternUtils.PROFILE_KEY_NAME_DECRYPT)) {
continue;
}
- Credential c = credentials.get(alias);
+ Credential c = aliasMap.get(alias);
if (c == null) {
- credentials.put(alias, (c = new Credential(alias)));
+ c = new Credential(alias, uid);
+ aliasMap.put(alias, c);
}
c.storedTypes.add(type);
}
}
- return credentials;
+ return aliasMap;
}
@Override
- protected void onPostExecute(SortedMap<String, Credential> credentials) {
- // Convert the results to an array and present them using an ArrayAdapter.
- mListView.setAdapter(new CredentialAdapter(getContext(), R.layout.user_credential,
- credentials.values().toArray(new Credential[0])));
+ protected void onPostExecute(List<Credential> credentials) {
+ final Credential[] credentialArray = credentials.toArray(new Credential[0]);
+ mListView.setAdapter(new CredentialAdapter(getContext(), credentialArray));
}
}
@@ -238,28 +285,56 @@
* Helper class to display {@link Credential}s in a list.
*/
private static class CredentialAdapter extends ArrayAdapter<Credential> {
- public CredentialAdapter(Context context, int resource, Credential[] objects) {
- super(context, resource, objects);
+ private static final int LAYOUT_RESOURCE = R.layout.user_credential_preference;
+
+ public CredentialAdapter(Context context, final Credential[] objects) {
+ super(context, LAYOUT_RESOURCE, objects);
}
@Override
- public View getView(int position, View view, ViewGroup parent) {
- if (view == null) {
- view = LayoutInflater.from(getContext())
- .inflate(R.layout.user_credential, parent, false);
- }
- Credential item = getItem(position);
- ((TextView) view.findViewById(R.id.alias)).setText(item.alias);
- view.findViewById(R.id.contents_userkey).setVisibility(
- item.storedTypes.contains(Credential.Type.USER_PRIVATE_KEY) ? VISIBLE : GONE);
- view.findViewById(R.id.contents_usercrt).setVisibility(
- item.storedTypes.contains(Credential.Type.USER_CERTIFICATE) ? VISIBLE : GONE);
- view.findViewById(R.id.contents_cacrt).setVisibility(
- item.storedTypes.contains(Credential.Type.CA_CERTIFICATE) ? VISIBLE : GONE);
- return view;
+ public View getView(int position, @Nullable View view, ViewGroup parent) {
+ return getCredentialView(getItem(position), LAYOUT_RESOURCE, view, parent,
+ /* expanded */ false);
}
}
+ /**
+ * Mapping from View IDs in {@link R} to the types of credentials they describe.
+ */
+ private static final SparseArray<Credential.Type> credentialViewTypes = new SparseArray<>();
+ static {
+ credentialViewTypes.put(R.id.contents_userkey, Credential.Type.USER_PRIVATE_KEY);
+ credentialViewTypes.put(R.id.contents_usercrt, Credential.Type.USER_CERTIFICATE);
+ credentialViewTypes.put(R.id.contents_cacrt, Credential.Type.CA_CERTIFICATE);
+ }
+
+ protected static View getCredentialView(Credential item, @LayoutRes int layoutResource,
+ @Nullable View view, ViewGroup parent, boolean expanded) {
+ if (view == null) {
+ view = LayoutInflater.from(parent.getContext()).inflate(layoutResource, parent, false);
+ }
+
+ ((TextView) view.findViewById(R.id.alias)).setText(item.alias);
+ ((TextView) view.findViewById(R.id.purpose)).setText(item.isSystem()
+ ? R.string.credential_for_vpn_and_apps
+ : R.string.credential_for_wifi);
+
+ view.findViewById(R.id.contents).setVisibility(expanded ? View.VISIBLE : View.GONE);
+ if (expanded) {
+ for (int i = 0; i < credentialViewTypes.size(); i++) {
+ final View detail = view.findViewById(credentialViewTypes.keyAt(i));
+ detail.setVisibility(item.storedTypes.contains(credentialViewTypes.valueAt(i))
+ ? View.VISIBLE : View.GONE);
+ }
+ }
+ return view;
+ }
+
+ static class AliasEntry {
+ public String alias;
+ public int uid;
+ }
+
static class Credential implements Parcelable {
static enum Type {
CA_CERTIFICATE (Credentials.CA_CERTIFICATE),
@@ -281,6 +356,12 @@
final String alias;
/**
+ * UID under which this credential is stored. Typically {@link Process#SYSTEM_UID} but can
+ * also be {@link Process#WIFI_UID} for credentials installed as wifi certificates.
+ */
+ final int uid;
+
+ /**
* Should contain some non-empty subset of:
* <ul>
* <li>{@link Credentials.CA_CERTIFICATE}</li>
@@ -291,12 +372,13 @@
*/
final EnumSet<Type> storedTypes = EnumSet.noneOf(Type.class);
- Credential(final String alias) {
+ Credential(final String alias, final int uid) {
this.alias = alias;
+ this.uid = uid;
}
Credential(Parcel in) {
- this(in.readString());
+ this(in.readString(), in.readInt());
long typeBits = in.readLong();
for (Type i : Type.values()) {
@@ -308,6 +390,7 @@
public void writeToParcel(Parcel out, int flags) {
out.writeString(alias);
+ out.writeInt(uid);
long typeBits = 0;
for (Type i : storedTypes) {
@@ -330,5 +413,9 @@
return new Credential[size];
}
};
+
+ public boolean isSystem() {
+ return UserHandle.getAppId(uid) == Process.SYSTEM_UID;
+ }
}
}