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
This commit is contained in:
Robin Lee
2016-07-19 16:10:39 +01:00
parent 14349303d7
commit e68d957777
5 changed files with 227 additions and 84 deletions

View File

@@ -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.AsyncTask;
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;
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.internal.widget.LockPatternUtils;
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 @@ import static android.view.View.VISIBLE;
public class UserCredentialsSettings extends OptionsMenuFragment implements OnItemClickListener {
private static final String TAG = "UserCredentialsSettings";
private View mRootView;
private ListView mListView;
@Override
@@ -76,13 +81,13 @@ public class UserCredentialsSettings extends OptionsMenuFragment implements OnIt
@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 @@ public class UserCredentialsSettings extends OptionsMenuFragment implements OnIt
@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 @@ public class UserCredentialsSettings extends OptionsMenuFragment implements OnIt
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 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();
}
return aliases;
}
@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 @@ public class UserCredentialsSettings extends OptionsMenuFragment implements OnIt
* 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 @@ public class UserCredentialsSettings extends OptionsMenuFragment implements OnIt
* 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),
@@ -280,6 +355,12 @@ public class UserCredentialsSettings extends OptionsMenuFragment implements OnIt
*/
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>
@@ -291,12 +372,13 @@ public class UserCredentialsSettings extends OptionsMenuFragment implements OnIt
*/
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 class UserCredentialsSettings extends OptionsMenuFragment implements OnIt
public void writeToParcel(Parcel out, int flags) {
out.writeString(alias);
out.writeInt(uid);
long typeBits = 0;
for (Type i : storedTypes) {
@@ -330,5 +413,9 @@ public class UserCredentialsSettings extends OptionsMenuFragment implements OnIt
return new Credential[size];
}
};
public boolean isSystem() {
return UserHandle.getAppId(uid) == Process.SYSTEM_UID;
}
}
}