2 * Copyright (C) 2015 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.settings;
19 import android.annotation.LayoutRes;
20 import android.annotation.Nullable;
21 import android.app.AlertDialog;
22 import android.app.Dialog;
23 import android.app.DialogFragment;
24 import android.app.Fragment;
25 import android.content.Context;
26 import android.content.DialogInterface;
27 import android.os.AsyncTask;
28 import android.os.Bundle;
29 import android.os.Parcel;
30 import android.os.Parcelable;
31 import android.os.Process;
32 import android.os.RemoteException;
33 import android.os.UserHandle;
34 import android.os.UserManager;
35 import android.security.Credentials;
36 import android.security.IKeyChainService;
37 import android.security.KeyChain;
38 import android.security.KeyChain.KeyChainConnection;
39 import android.security.KeyStore;
40 import android.support.v7.widget.RecyclerView;
41 import android.util.Log;
42 import android.util.SparseArray;
43 import android.view.LayoutInflater;
44 import android.view.View;
45 import android.view.ViewGroup;
46 import android.widget.TextView;
48 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
49 import com.android.internal.widget.LockPatternUtils;
50 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
51 import com.android.settings.SettingsPreferenceFragment;
52 import com.android.settingslib.RestrictedLockUtils;
53 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
55 import java.util.ArrayList;
56 import java.util.EnumSet;
57 import java.util.List;
58 import java.util.SortedMap;
59 import java.util.TreeMap;
61 import static android.view.View.GONE;
62 import static android.view.View.VISIBLE;
64 public class UserCredentialsSettings extends SettingsPreferenceFragment
65 implements View.OnClickListener {
66 private static final String TAG = "UserCredentialsSettings";
69 public int getMetricsCategory() {
70 return MetricsEvent.USER_CREDENTIALS;
74 public void onResume() {
80 public void onClick(final View view) {
81 final Credential item = (Credential) view.getTag();
83 CredentialDialogFragment.show(this, item);
87 protected void announceRemoval(String alias) {
91 getListView().announceForAccessibility(getString(R.string.user_credential_removed, alias));
94 protected void refreshItems() {
96 new AliasLoader().execute();
100 public static class CredentialDialogFragment extends InstrumentedDialogFragment {
101 private static final String TAG = "CredentialDialogFragment";
102 private static final String ARG_CREDENTIAL = "credential";
104 public static void show(Fragment target, Credential item) {
105 final Bundle args = new Bundle();
106 args.putParcelable(ARG_CREDENTIAL, item);
108 if (target.getFragmentManager().findFragmentByTag(TAG) == null) {
109 final DialogFragment frag = new CredentialDialogFragment();
110 frag.setTargetFragment(target, /* requestCode */ -1);
111 frag.setArguments(args);
112 frag.show(target.getFragmentManager(), TAG);
117 public Dialog onCreateDialog(Bundle savedInstanceState) {
118 final Credential item = (Credential) getArguments().getParcelable(ARG_CREDENTIAL);
120 View root = getActivity().getLayoutInflater()
121 .inflate(R.layout.user_credential_dialog, null);
122 ViewGroup infoContainer = (ViewGroup) root.findViewById(R.id.credential_container);
123 View contentView = getCredentialView(item, R.layout.user_credential, null,
124 infoContainer, /* expanded */ true);
125 infoContainer.addView(contentView);
127 AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
129 .setTitle(R.string.user_credential_title)
130 .setPositiveButton(R.string.done, null);
132 final String restriction = UserManager.DISALLOW_CONFIG_CREDENTIALS;
133 final int myUserId = UserHandle.myUserId();
134 if (!RestrictedLockUtils.hasBaseUserRestriction(getContext(), restriction, myUserId)) {
135 DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
136 @Override public void onClick(DialogInterface dialog, int id) {
137 final EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced(
138 getContext(), restriction, myUserId);
140 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(),
143 new RemoveCredentialsTask(getContext(), getTargetFragment())
149 if (item.isSystem()) {
150 // TODO: a safe means of clearing wifi certificates. Configs refer to aliases
151 // directly so deleting certs will break dependent access points.
152 builder.setNegativeButton(R.string.trusted_credentials_remove_label, listener);
155 return builder.create();
159 public int getMetricsCategory() {
160 return MetricsEvent.DIALOG_USER_CREDENTIAL;
164 * Deletes all certificates and keys under a given alias.
166 * If the {@link Credential} is for a system alias, all active grants to the alias will be
167 * removed using {@link KeyChain}.
169 private class RemoveCredentialsTask extends AsyncTask<Credential, Void, Credential[]> {
170 private Context context;
171 private Fragment targetFragment;
173 public RemoveCredentialsTask(Context context, Fragment targetFragment) {
174 this.context = context;
175 this.targetFragment = targetFragment;
179 protected Credential[] doInBackground(Credential... credentials) {
180 for (final Credential credential : credentials) {
181 if (credential.isSystem()) {
182 removeGrantsAndDelete(credential);
185 throw new UnsupportedOperationException(
186 "Not implemented for wifi certificates. This should not be reachable.");
191 private void removeGrantsAndDelete(final Credential credential) {
192 final KeyChainConnection conn;
194 conn = KeyChain.bind(getContext());
195 } catch (InterruptedException e) {
196 Log.w(TAG, "Connecting to KeyChain", e);
201 IKeyChainService keyChain = conn.getService();
202 keyChain.removeKeyPair(credential.alias);
203 } catch (RemoteException e) {
204 Log.w(TAG, "Removing credentials", e);
211 protected void onPostExecute(Credential... credentials) {
212 if (targetFragment instanceof UserCredentialsSettings && targetFragment.isAdded()) {
213 final UserCredentialsSettings target = (UserCredentialsSettings) targetFragment;
214 for (final Credential credential : credentials) {
215 target.announceRemoval(credential.alias);
217 target.refreshItems();
224 * Opens a background connection to KeyStore to list user credentials.
225 * The credentials are stored in a {@link CredentialAdapter} attached to the main
226 * {@link ListView} in the fragment.
228 private class AliasLoader extends AsyncTask<Void, Void, List<Credential>> {
230 * @return a list of credentials ordered:
232 * <li>first by purpose;</li>
233 * <li>then by alias.</li>
237 protected List<Credential> doInBackground(Void... params) {
238 final KeyStore keyStore = KeyStore.getInstance();
240 // Certificates can be installed into SYSTEM_UID or WIFI_UID through CertInstaller.
241 final int myUserId = UserHandle.myUserId();
242 final int systemUid = UserHandle.getUid(myUserId, Process.SYSTEM_UID);
243 final int wifiUid = UserHandle.getUid(myUserId, Process.WIFI_UID);
245 List<Credential> credentials = new ArrayList<>();
246 credentials.addAll(getCredentialsForUid(keyStore, systemUid).values());
247 credentials.addAll(getCredentialsForUid(keyStore, wifiUid).values());
251 private SortedMap<String, Credential> getCredentialsForUid(KeyStore keyStore, int uid) {
252 final SortedMap<String, Credential> aliasMap = new TreeMap<>();
253 for (final Credential.Type type : Credential.Type.values()) {
254 for (final String alias : keyStore.list(type.prefix, uid)) {
255 if (UserHandle.getAppId(uid) == Process.SYSTEM_UID) {
256 // Do not show work profile keys in user credentials
257 if (alias.startsWith(LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT) ||
258 alias.startsWith(LockPatternUtils.PROFILE_KEY_NAME_DECRYPT)) {
261 // Do not show synthetic password keys in user credential
262 if (alias.startsWith(LockPatternUtils.SYNTHETIC_PASSWORD_KEY_PREFIX)) {
266 Credential c = aliasMap.get(alias);
268 c = new Credential(alias, uid);
269 aliasMap.put(alias, c);
271 c.storedTypes.add(type);
278 protected void onPostExecute(List<Credential> credentials) {
283 if (credentials == null || credentials.size() == 0) {
284 // Create a "no credentials installed" message for the empty case.
285 TextView emptyTextView = (TextView) getActivity().findViewById(android.R.id.empty);
286 emptyTextView.setText(R.string.user_credential_none_installed);
287 setEmptyView(emptyTextView);
292 getListView().setAdapter(
293 new CredentialAdapter(credentials, UserCredentialsSettings.this));
298 * Helper class to display {@link Credential}s in a list.
300 private static class CredentialAdapter extends RecyclerView.Adapter<ViewHolder> {
301 private static final int LAYOUT_RESOURCE = R.layout.user_credential_preference;
303 private final List<Credential> mItems;
304 private final View.OnClickListener mListener;
306 public CredentialAdapter(List<Credential> items, @Nullable View.OnClickListener listener) {
308 mListener = listener;
312 public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
313 final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
314 return new ViewHolder(inflater.inflate(LAYOUT_RESOURCE, parent, false));
318 public void onBindViewHolder(ViewHolder h, int position) {
319 getCredentialView(mItems.get(position), LAYOUT_RESOURCE, h.itemView, null, false);
320 h.itemView.setTag(mItems.get(position));
321 h.itemView.setOnClickListener(mListener);
325 public int getItemCount() {
326 return mItems.size();
330 private static class ViewHolder extends RecyclerView.ViewHolder {
331 public ViewHolder(View item) {
337 * Mapping from View IDs in {@link R} to the types of credentials they describe.
339 private static final SparseArray<Credential.Type> credentialViewTypes = new SparseArray<>();
341 credentialViewTypes.put(R.id.contents_userkey, Credential.Type.USER_PRIVATE_KEY);
342 credentialViewTypes.put(R.id.contents_usercrt, Credential.Type.USER_CERTIFICATE);
343 credentialViewTypes.put(R.id.contents_cacrt, Credential.Type.CA_CERTIFICATE);
346 protected static View getCredentialView(Credential item, @LayoutRes int layoutResource,
347 @Nullable View view, ViewGroup parent, boolean expanded) {
349 view = LayoutInflater.from(parent.getContext()).inflate(layoutResource, parent, false);
352 ((TextView) view.findViewById(R.id.alias)).setText(item.alias);
353 ((TextView) view.findViewById(R.id.purpose)).setText(item.isSystem()
354 ? R.string.credential_for_vpn_and_apps
355 : R.string.credential_for_wifi);
357 view.findViewById(R.id.contents).setVisibility(expanded ? View.VISIBLE : View.GONE);
359 for (int i = 0; i < credentialViewTypes.size(); i++) {
360 final View detail = view.findViewById(credentialViewTypes.keyAt(i));
361 detail.setVisibility(item.storedTypes.contains(credentialViewTypes.valueAt(i))
362 ? View.VISIBLE : View.GONE);
368 static class AliasEntry {
373 static class Credential implements Parcelable {
375 CA_CERTIFICATE (Credentials.CA_CERTIFICATE),
376 USER_CERTIFICATE (Credentials.USER_CERTIFICATE),
377 USER_PRIVATE_KEY (Credentials.USER_PRIVATE_KEY),
378 USER_SECRET_KEY (Credentials.USER_SECRET_KEY);
382 Type(String prefix) {
383 this.prefix = prefix;
388 * Main part of the credential's alias. To fetch an item from KeyStore, prepend one of the
389 * prefixes from {@link CredentialItem.storedTypes}.
394 * UID under which this credential is stored. Typically {@link Process#SYSTEM_UID} but can
395 * also be {@link Process#WIFI_UID} for credentials installed as wifi certificates.
400 * Should contain some non-empty subset of:
402 * <li>{@link Credentials.CA_CERTIFICATE}</li>
403 * <li>{@link Credentials.USER_CERTIFICATE}</li>
404 * <li>{@link Credentials.USER_PRIVATE_KEY}</li>
405 * <li>{@link Credentials.USER_SECRET_KEY}</li>
408 final EnumSet<Type> storedTypes = EnumSet.noneOf(Type.class);
410 Credential(final String alias, final int uid) {
415 Credential(Parcel in) {
416 this(in.readString(), in.readInt());
418 long typeBits = in.readLong();
419 for (Type i : Type.values()) {
420 if ((typeBits & (1L << i.ordinal())) != 0L) {
426 public void writeToParcel(Parcel out, int flags) {
427 out.writeString(alias);
431 for (Type i : storedTypes) {
432 typeBits |= 1L << i.ordinal();
434 out.writeLong(typeBits);
437 public int describeContents() {
441 public static final Parcelable.Creator<Credential> CREATOR
442 = new Parcelable.Creator<Credential>() {
443 public Credential createFromParcel(Parcel in) {
444 return new Credential(in);
447 public Credential[] newArray(int size) {
448 return new Credential[size];
452 public boolean isSystem() {
453 return UserHandle.getAppId(uid) == Process.SYSTEM_UID;