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

@@ -13,52 +13,65 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center_vertical"
android:background="?android:attr/selectableItemBackground"
android:paddingTop="15dip"
android:paddingBottom="15dip">
android:orientation="vertical">
<TextView
android:id="@+id/alias"
android:layout_width="fill_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textAppearance="?android:attr/textAppearanceMedium" />
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
<TextView
android:id="@+id/purpose"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1">
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorSecondary" />
<LinearLayout
android:id="@+id/contents"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="10dp">
<TextView
android:id="@+id/contents_title"
android:text="@string/credential_contains"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorSecondary"/>
<TextView
android:id="@+id/contents_userkey"
android:text="@string/one_userkey"
android:layout_width="fill_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorSecondary" />
android:textColor="?android:attr/textColorTertiary"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"/>
<TextView
android:id="@+id/contents_usercrt"
android:text="@string/one_usercrt"
android:layout_width="fill_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorSecondary" />
android:textColor="?android:attr/textColorTertiary"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"/>
<TextView
android:id="@+id/contents_cacrt"
android:text="@string/one_cacrt"
android:layout_width="fill_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorSecondary" />
android:textColor="?android:attr/textColorTertiary"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"/>
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2016 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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:background="?android:attr/selectableItemBackground"
android:paddingTop="15dip"
android:paddingBottom="15dip">
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingEnd="?android:attr/listPreferredItemPaddingStart"
android:src="@android:drawable/ic_lock_lock"
android:tint="?android:attr/colorAccent"/>
<include layout="@layout/user_credential"/>
</LinearLayout>

View File

@@ -4643,6 +4643,10 @@
<string name="credential_storage_type_software">Software only</string>
<!-- Error message for users that aren't allowed to see or modify credentials [CHAR LIMIT=none] -->
<string name="credentials_settings_not_available">Credentials are not available for this user</string>
<!-- Sub-heading for a user credential installed to be used by apps and as part of VPN configurations. [CHAR LIMIT=NONE] -->
<string name="credential_for_vpn_and_apps">Installed for VPN and apps</string>
<!-- Sub-heading for a user credential installed to be used as part of a Wi-Fi configuration. [CHAR LIMIT=NONE]. -->
<string name="credential_for_wifi">Installed for Wi-Fi</string>
<!-- Title of dialog to enable credential storage [CHAR LIMIT=30] -->
<string name="credentials_unlock"></string>
@@ -5331,6 +5335,8 @@
<!-- Alert dialog confirmation when removing a user CA certificate. -->
<string name="trusted_credentials_remove_confirmation">Permanently remove the user CA certificate?</string>
<!-- Header for a list of items that a credential entry contains. For example, one private key and one certificate. [CHAR LIMIT=NONE] -->
<string name="credential_contains">This entry contains:</string>
<!-- Item found in the PKCS12 keystore being investigated [CHAR LIMIT=NONE] -->
<string name="one_userkey">one user key</string>
<!-- Item found in the PKCS12 keystore being investigated [CHAR LIMIT=NONE] -->

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();
}
};
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) {
protected Credential[] doInBackground(Credential... credentials) {
for (final Credential credential : credentials) {
if (credential.isSystem()) {
removeGrantsAndDelete(credential);
continue;
}
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 {
final KeyChainConnection conn = KeyChain.bind(getContext());
conn = KeyChain.bind(getContext());
} catch (InterruptedException e) {
Log.w(TAG, "Connecting to KeyChain", e);
return;
}
try {
IKeyChainService keyChain = conn.getService();
for (String alias : aliases) {
keyChain.removeKeyPair(alias);
}
keyChain.removeKeyPair(credential.alias);
} catch (RemoteException e) {
Log.w(TAG, "Removing credentials", e);
} finally {
conn.close();
}
} catch (InterruptedException e) {
Log.w(TAG, "Connecting to keychain", e);
}
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,26 +285,54 @@ 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);
public View getView(int position, @Nullable View view, ViewGroup parent) {
return getCredentialView(getItem(position), LAYOUT_RESOURCE, view, parent,
/* expanded */ false);
}
Credential item = getItem(position);
}
/**
* 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);
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);
((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 {
@@ -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;
}
}
}

View File

@@ -17,6 +17,7 @@
package com.android.settings;
import android.os.Parcel;
import android.os.Process;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.SmallTest;
@@ -36,7 +37,7 @@ public class UserCredentialsTest extends InstrumentationTestCase {
@SmallTest
public void testCredentialIsParcelable() {
final String alias = "credential-test-alias";
Credential c = new Credential(alias);
Credential c = new Credential(alias, Process.SYSTEM_UID);
c.storedTypes.add(Credential.Type.CA_CERTIFICATE);
c.storedTypes.add(Credential.Type.USER_SECRET_KEY);
@@ -47,6 +48,7 @@ public class UserCredentialsTest extends InstrumentationTestCase {
Credential r = Credential.CREATOR.createFromParcel(p);
assertEquals(c.alias, r.alias);
assertEquals(c.uid, r.uid);
assertEquals(c.storedTypes, r.storedTypes);
}
}