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:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user