OSDN Git Service

Merge changes from topic "am-e8b13d24-dee0-4f84-89d4-8a7d110f6ec1" into oc-dev am...
[android-x86/packages-apps-Settings.git] / src / com / android / settings / UserCredentialsSettings.java
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.android.settings;
18
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;
47
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;
54
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;
60
61 import static android.view.View.GONE;
62 import static android.view.View.VISIBLE;
63
64 public class UserCredentialsSettings extends SettingsPreferenceFragment
65         implements View.OnClickListener {
66     private static final String TAG = "UserCredentialsSettings";
67
68     @Override
69     public int getMetricsCategory() {
70         return MetricsEvent.USER_CREDENTIALS;
71     }
72
73     @Override
74     public void onResume() {
75         super.onResume();
76         refreshItems();
77     }
78
79     @Override
80     public void onClick(final View view) {
81         final Credential item = (Credential) view.getTag();
82         if (item != null) {
83             CredentialDialogFragment.show(this, item);
84         }
85     }
86
87     protected void announceRemoval(String alias) {
88         if (!isAdded()) {
89             return;
90         }
91         getListView().announceForAccessibility(getString(R.string.user_credential_removed, alias));
92     }
93
94     protected void refreshItems() {
95         if (isAdded()) {
96             new AliasLoader().execute();
97         }
98     }
99
100     public static class CredentialDialogFragment extends InstrumentedDialogFragment {
101         private static final String TAG = "CredentialDialogFragment";
102         private static final String ARG_CREDENTIAL = "credential";
103
104         public static void show(Fragment target, Credential item) {
105             final Bundle args = new Bundle();
106             args.putParcelable(ARG_CREDENTIAL, item);
107
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);
113             }
114         }
115
116         @Override
117         public Dialog onCreateDialog(Bundle savedInstanceState) {
118             final Credential item = (Credential) getArguments().getParcelable(ARG_CREDENTIAL);
119
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);
126
127             AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
128                     .setView(root)
129                     .setTitle(R.string.user_credential_title)
130                     .setPositiveButton(R.string.done, null);
131
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);
139                         if (admin != null) {
140                             RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(),
141                                     admin);
142                         } else {
143                             new RemoveCredentialsTask(getContext(), getTargetFragment())
144                                     .execute(item);
145                         }
146                         dialog.dismiss();
147                     }
148                 };
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);
153                 }
154             }
155             return builder.create();
156         }
157
158         @Override
159         public int getMetricsCategory() {
160             return MetricsEvent.DIALOG_USER_CREDENTIAL;
161         }
162
163         /**
164          * Deletes all certificates and keys under a given alias.
165          *
166          * If the {@link Credential} is for a system alias, all active grants to the alias will be
167          * removed using {@link KeyChain}.
168          */
169         private class RemoveCredentialsTask extends AsyncTask<Credential, Void, Credential[]> {
170             private Context context;
171             private Fragment targetFragment;
172
173             public RemoveCredentialsTask(Context context, Fragment targetFragment) {
174                 this.context = context;
175                 this.targetFragment = targetFragment;
176             }
177
178             @Override
179             protected Credential[] doInBackground(Credential... credentials) {
180                 for (final Credential credential : credentials) {
181                     if (credential.isSystem()) {
182                         removeGrantsAndDelete(credential);
183                         continue;
184                     }
185                     throw new UnsupportedOperationException(
186                             "Not implemented for wifi certificates. This should not be reachable.");
187                 }
188                 return credentials;
189             }
190
191             private void removeGrantsAndDelete(final Credential credential) {
192                 final KeyChainConnection conn;
193                 try {
194                     conn = KeyChain.bind(getContext());
195                 } catch (InterruptedException e) {
196                     Log.w(TAG, "Connecting to KeyChain", e);
197                     return;
198                 }
199
200                 try {
201                     IKeyChainService keyChain = conn.getService();
202                     keyChain.removeKeyPair(credential.alias);
203                 } catch (RemoteException e) {
204                     Log.w(TAG, "Removing credentials", e);
205                 } finally {
206                     conn.close();
207                 }
208             }
209
210             @Override
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);
216                     }
217                     target.refreshItems();
218                 }
219             }
220         }
221     }
222
223     /**
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.
227      */
228     private class AliasLoader extends AsyncTask<Void, Void, List<Credential>> {
229         /**
230          * @return a list of credentials ordered:
231          * <ol>
232          *   <li>first by purpose;</li>
233          *   <li>then by alias.</li>
234          * </ol>
235          */
236         @Override
237         protected List<Credential> doInBackground(Void... params) {
238             final KeyStore keyStore = KeyStore.getInstance();
239
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);
244
245             List<Credential> credentials = new ArrayList<>();
246             credentials.addAll(getCredentialsForUid(keyStore, systemUid).values());
247             credentials.addAll(getCredentialsForUid(keyStore, wifiUid).values());
248             return credentials;
249         }
250
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)) {
259                             continue;
260                         }
261                         // Do not show synthetic password keys in user credential
262                         if (alias.startsWith(LockPatternUtils.SYNTHETIC_PASSWORD_KEY_PREFIX)) {
263                             continue;
264                         }
265                     }
266                     Credential c = aliasMap.get(alias);
267                     if (c == null) {
268                         c = new Credential(alias, uid);
269                         aliasMap.put(alias, c);
270                     }
271                     c.storedTypes.add(type);
272                 }
273             }
274             return aliasMap;
275         }
276
277         @Override
278         protected void onPostExecute(List<Credential> credentials) {
279             if (!isAdded()) {
280                 return;
281             }
282
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);
288             } else {
289                 setEmptyView(null);
290             }
291
292             getListView().setAdapter(
293                     new CredentialAdapter(credentials, UserCredentialsSettings.this));
294         }
295     }
296
297     /**
298      * Helper class to display {@link Credential}s in a list.
299      */
300     private static class CredentialAdapter extends RecyclerView.Adapter<ViewHolder> {
301         private static final int LAYOUT_RESOURCE = R.layout.user_credential_preference;
302
303         private final List<Credential> mItems;
304         private final View.OnClickListener mListener;
305
306         public CredentialAdapter(List<Credential> items, @Nullable View.OnClickListener listener) {
307             mItems = items;
308             mListener = listener;
309         }
310
311         @Override
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));
315         }
316
317         @Override
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);
322         }
323
324         @Override
325         public int getItemCount() {
326             return mItems.size();
327         }
328     }
329
330     private static class ViewHolder extends RecyclerView.ViewHolder {
331         public ViewHolder(View item) {
332             super(item);
333         }
334     }
335
336     /**
337      * Mapping from View IDs in {@link R} to the types of credentials they describe.
338      */
339     private static final SparseArray<Credential.Type> credentialViewTypes = new SparseArray<>();
340     static {
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);
344     }
345
346     protected static View getCredentialView(Credential item, @LayoutRes int layoutResource,
347             @Nullable View view, ViewGroup parent, boolean expanded) {
348         if (view == null) {
349             view = LayoutInflater.from(parent.getContext()).inflate(layoutResource, parent, false);
350         }
351
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);
356
357         view.findViewById(R.id.contents).setVisibility(expanded ? View.VISIBLE : View.GONE);
358         if (expanded) {
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);
363             }
364         }
365         return view;
366     }
367
368     static class AliasEntry {
369         public String alias;
370         public int uid;
371     }
372
373     static class Credential implements Parcelable {
374         static enum Type {
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);
379
380             final String prefix;
381
382             Type(String prefix) {
383                 this.prefix = prefix;
384             }
385         }
386
387         /**
388          * Main part of the credential's alias. To fetch an item from KeyStore, prepend one of the
389          * prefixes from {@link CredentialItem.storedTypes}.
390          */
391         final String alias;
392
393         /**
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.
396          */
397         final int uid;
398
399         /**
400          * Should contain some non-empty subset of:
401          * <ul>
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>
406          * </ul>
407          */
408         final EnumSet<Type> storedTypes = EnumSet.noneOf(Type.class);
409
410         Credential(final String alias, final int uid) {
411             this.alias = alias;
412             this.uid = uid;
413         }
414
415         Credential(Parcel in) {
416             this(in.readString(), in.readInt());
417
418             long typeBits = in.readLong();
419             for (Type i : Type.values()) {
420                 if ((typeBits & (1L << i.ordinal())) != 0L) {
421                     storedTypes.add(i);
422                 }
423             }
424         }
425
426         public void writeToParcel(Parcel out, int flags) {
427             out.writeString(alias);
428             out.writeInt(uid);
429
430             long typeBits = 0;
431             for (Type i : storedTypes) {
432                 typeBits |= 1L << i.ordinal();
433             }
434             out.writeLong(typeBits);
435         }
436
437         public int describeContents() {
438             return 0;
439         }
440
441         public static final Parcelable.Creator<Credential> CREATOR
442                 = new Parcelable.Creator<Credential>() {
443             public Credential createFromParcel(Parcel in) {
444                 return new Credential(in);
445             }
446
447             public Credential[] newArray(int size) {
448                 return new Credential[size];
449             }
450         };
451
452         public boolean isSystem() {
453             return UserHandle.getAppId(uid) == Process.SYSTEM_UID;
454         }
455     }
456 }