blob: f34b7a771cdf58f14a2563dabbc37d101ea50690 [file] [log] [blame]
Robin Leebaefdcf2015-08-26 10:57:44 +01001/*
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
17package com.android.settings;
18
Robin Leee68d9572016-07-19 16:10:39 +010019import android.annotation.LayoutRes;
20import android.annotation.Nullable;
Doris Ling03a3b512017-10-18 14:25:01 -070021import android.annotation.StringRes;
Robin Leebaefdcf2015-08-26 10:57:44 +010022import android.app.AlertDialog;
Robin Lee04046a12016-01-19 11:42:57 +000023import android.app.Dialog;
24import android.app.DialogFragment;
25import android.app.Fragment;
Robin Leebaefdcf2015-08-26 10:57:44 +010026import android.content.Context;
27import android.content.DialogInterface;
28import android.os.AsyncTask;
29import android.os.Bundle;
Robin Lee04046a12016-01-19 11:42:57 +000030import android.os.Parcel;
31import android.os.Parcelable;
Robin Leee68d9572016-07-19 16:10:39 +010032import android.os.Process;
Robin Leeda7bc512016-02-24 17:39:32 +000033import android.os.RemoteException;
Robin Leec421db72016-03-11 16:22:23 +000034import android.os.UserHandle;
35import android.os.UserManager;
Robin Leebaefdcf2015-08-26 10:57:44 +010036import android.security.Credentials;
Robin Leeda7bc512016-02-24 17:39:32 +000037import android.security.IKeyChainService;
38import android.security.KeyChain;
39import android.security.KeyChain.KeyChainConnection;
Robin Leebaefdcf2015-08-26 10:57:44 +010040import android.security.KeyStore;
Robin Leeccaf9c92017-03-24 14:50:05 +000041import android.support.v7.widget.RecyclerView;
Robin Leeda7bc512016-02-24 17:39:32 +000042import android.util.Log;
Robin Leee68d9572016-07-19 16:10:39 +010043import android.util.SparseArray;
Robin Leebaefdcf2015-08-26 10:57:44 +010044import android.view.LayoutInflater;
45import android.view.View;
46import android.view.ViewGroup;
Robin Leebaefdcf2015-08-26 10:57:44 +010047import android.widget.TextView;
48
Tamas Berghammer265d3c22016-06-22 15:34:45 +010049import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
Ricky Wai95792742016-05-24 19:28:53 +010050import com.android.internal.widget.LockPatternUtils;
Fan Zhang1e516282016-09-16 12:45:07 -070051import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
Robin Leeccaf9c92017-03-24 14:50:05 +000052import com.android.settings.SettingsPreferenceFragment;
Robin Leec421db72016-03-11 16:22:23 +000053import com.android.settingslib.RestrictedLockUtils;
54import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
Robin Leeb70b3d82016-02-01 12:52:16 +000055
Robin Leee68d9572016-07-19 16:10:39 +010056import java.util.ArrayList;
Robin Leebaefdcf2015-08-26 10:57:44 +010057import java.util.EnumSet;
Robin Leee68d9572016-07-19 16:10:39 +010058import java.util.List;
Robin Leebaefdcf2015-08-26 10:57:44 +010059import java.util.SortedMap;
60import java.util.TreeMap;
61
Robin Leebaefdcf2015-08-26 10:57:44 +010062import static android.view.View.GONE;
Jason Monk39b46742015-09-10 15:52:51 -040063import static android.view.View.VISIBLE;
Robin Leebaefdcf2015-08-26 10:57:44 +010064
Robin Leeccaf9c92017-03-24 14:50:05 +000065public class UserCredentialsSettings extends SettingsPreferenceFragment
66 implements View.OnClickListener {
Robin Leebaefdcf2015-08-26 10:57:44 +010067 private static final String TAG = "UserCredentialsSettings";
68
Robin Leebaefdcf2015-08-26 10:57:44 +010069 @Override
Fan Zhang65076132016-08-08 10:25:13 -070070 public int getMetricsCategory() {
Robin Leeb70b3d82016-02-01 12:52:16 +000071 return MetricsEvent.USER_CREDENTIALS;
Robin Leebaefdcf2015-08-26 10:57:44 +010072 }
73
74 @Override
75 public void onResume() {
76 super.onResume();
Robin Lee04046a12016-01-19 11:42:57 +000077 refreshItems();
Robin Leebaefdcf2015-08-26 10:57:44 +010078 }
79
80 @Override
Robin Leeccaf9c92017-03-24 14:50:05 +000081 public void onClick(final View view) {
82 final Credential item = (Credential) view.getTag();
83 if (item != null) {
84 CredentialDialogFragment.show(this, item);
85 }
Robin Lee04046a12016-01-19 11:42:57 +000086 }
Robin Leebaefdcf2015-08-26 10:57:44 +010087
Doris Ling03a3b512017-10-18 14:25:01 -070088 @Override
89 @StringRes
90 protected int getTitle() {
91 return R.string.user_credentials;
92 }
93
Robin Lee11fd5502016-05-16 15:42:34 +010094 protected void announceRemoval(String alias) {
Robin Leeccaf9c92017-03-24 14:50:05 +000095 if (!isAdded()) {
96 return;
Robin Lee11fd5502016-05-16 15:42:34 +010097 }
Robin Leeccaf9c92017-03-24 14:50:05 +000098 getListView().announceForAccessibility(getString(R.string.user_credential_removed, alias));
Robin Lee11fd5502016-05-16 15:42:34 +010099 }
100
Robin Lee04046a12016-01-19 11:42:57 +0000101 protected void refreshItems() {
102 if (isAdded()) {
103 new AliasLoader().execute();
104 }
105 }
Robin Leebaefdcf2015-08-26 10:57:44 +0100106
Fan Zhang1e516282016-09-16 12:45:07 -0700107 public static class CredentialDialogFragment extends InstrumentedDialogFragment {
Robin Lee04046a12016-01-19 11:42:57 +0000108 private static final String TAG = "CredentialDialogFragment";
109 private static final String ARG_CREDENTIAL = "credential";
110
111 public static void show(Fragment target, Credential item) {
112 final Bundle args = new Bundle();
113 args.putParcelable(ARG_CREDENTIAL, item);
114
Robin Leef8e2dbf2016-04-07 13:17:24 +0100115 if (target.getFragmentManager().findFragmentByTag(TAG) == null) {
116 final DialogFragment frag = new CredentialDialogFragment();
117 frag.setTargetFragment(target, /* requestCode */ -1);
118 frag.setArguments(args);
119 frag.show(target.getFragmentManager(), TAG);
120 }
Robin Lee04046a12016-01-19 11:42:57 +0000121 }
122
123 @Override
124 public Dialog onCreateDialog(Bundle savedInstanceState) {
125 final Credential item = (Credential) getArguments().getParcelable(ARG_CREDENTIAL);
Robin Leee68d9572016-07-19 16:10:39 +0100126
Robin Lee04046a12016-01-19 11:42:57 +0000127 View root = getActivity().getLayoutInflater()
128 .inflate(R.layout.user_credential_dialog, null);
129 ViewGroup infoContainer = (ViewGroup) root.findViewById(R.id.credential_container);
Robin Leee68d9572016-07-19 16:10:39 +0100130 View contentView = getCredentialView(item, R.layout.user_credential, null,
131 infoContainer, /* expanded */ true);
132 infoContainer.addView(contentView);
Robin Leec421db72016-03-11 16:22:23 +0000133
134 AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
Robin Lee04046a12016-01-19 11:42:57 +0000135 .setView(root)
136 .setTitle(R.string.user_credential_title)
Robin Leec421db72016-03-11 16:22:23 +0000137 .setPositiveButton(R.string.done, null);
138
139 final String restriction = UserManager.DISALLOW_CONFIG_CREDENTIALS;
140 final int myUserId = UserHandle.myUserId();
141 if (!RestrictedLockUtils.hasBaseUserRestriction(getContext(), restriction, myUserId)) {
142 DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
143 @Override public void onClick(DialogInterface dialog, int id) {
144 final EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced(
145 getContext(), restriction, myUserId);
146 if (admin != null) {
147 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(),
148 admin);
149 } else {
Robin Leee68d9572016-07-19 16:10:39 +0100150 new RemoveCredentialsTask(getContext(), getTargetFragment())
151 .execute(item);
Robin Leec421db72016-03-11 16:22:23 +0000152 }
153 dialog.dismiss();
154 }
155 };
Robin Leee68d9572016-07-19 16:10:39 +0100156 if (item.isSystem()) {
157 // TODO: a safe means of clearing wifi certificates. Configs refer to aliases
158 // directly so deleting certs will break dependent access points.
159 builder.setNegativeButton(R.string.trusted_credentials_remove_label, listener);
160 }
Robin Leec421db72016-03-11 16:22:23 +0000161 }
162 return builder.create();
Robin Lee04046a12016-01-19 11:42:57 +0000163 }
164
Fan Zhang1e516282016-09-16 12:45:07 -0700165 @Override
166 public int getMetricsCategory() {
167 return MetricsEvent.DIALOG_USER_CREDENTIAL;
168 }
169
Robin Leee68d9572016-07-19 16:10:39 +0100170 /**
171 * Deletes all certificates and keys under a given alias.
172 *
173 * If the {@link Credential} is for a system alias, all active grants to the alias will be
174 * removed using {@link KeyChain}.
175 */
176 private class RemoveCredentialsTask extends AsyncTask<Credential, Void, Credential[]> {
177 private Context context;
Robin Leeda7bc512016-02-24 17:39:32 +0000178 private Fragment targetFragment;
179
Robin Leee68d9572016-07-19 16:10:39 +0100180 public RemoveCredentialsTask(Context context, Fragment targetFragment) {
181 this.context = context;
Robin Leeda7bc512016-02-24 17:39:32 +0000182 this.targetFragment = targetFragment;
Robin Lee04046a12016-01-19 11:42:57 +0000183 }
Robin Leeda7bc512016-02-24 17:39:32 +0000184
185 @Override
Robin Leee68d9572016-07-19 16:10:39 +0100186 protected Credential[] doInBackground(Credential... credentials) {
187 for (final Credential credential : credentials) {
188 if (credential.isSystem()) {
189 removeGrantsAndDelete(credential);
190 continue;
Robin Leeda7bc512016-02-24 17:39:32 +0000191 }
Robin Leee68d9572016-07-19 16:10:39 +0100192 throw new UnsupportedOperationException(
193 "Not implemented for wifi certificates. This should not be reachable.");
Robin Leeda7bc512016-02-24 17:39:32 +0000194 }
Robin Leee68d9572016-07-19 16:10:39 +0100195 return credentials;
196 }
197
198 private void removeGrantsAndDelete(final Credential credential) {
199 final KeyChainConnection conn;
200 try {
201 conn = KeyChain.bind(getContext());
202 } catch (InterruptedException e) {
203 Log.w(TAG, "Connecting to KeyChain", e);
204 return;
205 }
206
207 try {
208 IKeyChainService keyChain = conn.getService();
209 keyChain.removeKeyPair(credential.alias);
210 } catch (RemoteException e) {
211 Log.w(TAG, "Removing credentials", e);
212 } finally {
213 conn.close();
214 }
Robin Leeda7bc512016-02-24 17:39:32 +0000215 }
216
217 @Override
Robin Leee68d9572016-07-19 16:10:39 +0100218 protected void onPostExecute(Credential... credentials) {
Robin Lee11fd5502016-05-16 15:42:34 +0100219 if (targetFragment instanceof UserCredentialsSettings && targetFragment.isAdded()) {
220 final UserCredentialsSettings target = (UserCredentialsSettings) targetFragment;
Robin Leee68d9572016-07-19 16:10:39 +0100221 for (final Credential credential : credentials) {
222 target.announceRemoval(credential.alias);
Robin Lee11fd5502016-05-16 15:42:34 +0100223 }
224 target.refreshItems();
Robin Leeda7bc512016-02-24 17:39:32 +0000225 }
226 }
Robin Lee04046a12016-01-19 11:42:57 +0000227 }
Robin Leebaefdcf2015-08-26 10:57:44 +0100228 }
229
230 /**
231 * Opens a background connection to KeyStore to list user credentials.
232 * The credentials are stored in a {@link CredentialAdapter} attached to the main
233 * {@link ListView} in the fragment.
234 */
Robin Leee68d9572016-07-19 16:10:39 +0100235 private class AliasLoader extends AsyncTask<Void, Void, List<Credential>> {
236 /**
237 * @return a list of credentials ordered:
238 * <ol>
239 * <li>first by purpose;</li>
240 * <li>then by alias.</li>
241 * </ol>
242 */
Robin Leebaefdcf2015-08-26 10:57:44 +0100243 @Override
Robin Leee68d9572016-07-19 16:10:39 +0100244 protected List<Credential> doInBackground(Void... params) {
245 final KeyStore keyStore = KeyStore.getInstance();
246
247 // Certificates can be installed into SYSTEM_UID or WIFI_UID through CertInstaller.
248 final int myUserId = UserHandle.myUserId();
249 final int systemUid = UserHandle.getUid(myUserId, Process.SYSTEM_UID);
250 final int wifiUid = UserHandle.getUid(myUserId, Process.WIFI_UID);
251
252 List<Credential> credentials = new ArrayList<>();
253 credentials.addAll(getCredentialsForUid(keyStore, systemUid).values());
254 credentials.addAll(getCredentialsForUid(keyStore, wifiUid).values());
255 return credentials;
256 }
257
258 private SortedMap<String, Credential> getCredentialsForUid(KeyStore keyStore, int uid) {
259 final SortedMap<String, Credential> aliasMap = new TreeMap<>();
Robin Leebaefdcf2015-08-26 10:57:44 +0100260 for (final Credential.Type type : Credential.Type.values()) {
Robin Leee68d9572016-07-19 16:10:39 +0100261 for (final String alias : keyStore.list(type.prefix, uid)) {
Rubin Xu52221d82017-02-09 11:09:11 +0000262 if (UserHandle.getAppId(uid) == Process.SYSTEM_UID) {
263 // Do not show work profile keys in user credentials
264 if (alias.startsWith(LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT) ||
265 alias.startsWith(LockPatternUtils.PROFILE_KEY_NAME_DECRYPT)) {
266 continue;
267 }
268 // Do not show synthetic password keys in user credential
269 if (alias.startsWith(LockPatternUtils.SYNTHETIC_PASSWORD_KEY_PREFIX)) {
270 continue;
271 }
Ricky Wai95792742016-05-24 19:28:53 +0100272 }
Robin Leee68d9572016-07-19 16:10:39 +0100273 Credential c = aliasMap.get(alias);
Robin Leebaefdcf2015-08-26 10:57:44 +0100274 if (c == null) {
Robin Leee68d9572016-07-19 16:10:39 +0100275 c = new Credential(alias, uid);
276 aliasMap.put(alias, c);
Robin Leebaefdcf2015-08-26 10:57:44 +0100277 }
278 c.storedTypes.add(type);
279 }
280 }
Robin Leee68d9572016-07-19 16:10:39 +0100281 return aliasMap;
Robin Leebaefdcf2015-08-26 10:57:44 +0100282 }
283
284 @Override
Robin Leee68d9572016-07-19 16:10:39 +0100285 protected void onPostExecute(List<Credential> credentials) {
Robin Leeccaf9c92017-03-24 14:50:05 +0000286 if (!isAdded()) {
287 return;
288 }
289
290 if (credentials == null || credentials.size() == 0) {
291 // Create a "no credentials installed" message for the empty case.
292 TextView emptyTextView = (TextView) getActivity().findViewById(android.R.id.empty);
293 emptyTextView.setText(R.string.user_credential_none_installed);
294 setEmptyView(emptyTextView);
295 } else {
296 setEmptyView(null);
297 }
298
299 getListView().setAdapter(
300 new CredentialAdapter(credentials, UserCredentialsSettings.this));
Robin Leebaefdcf2015-08-26 10:57:44 +0100301 }
302 }
303
304 /**
305 * Helper class to display {@link Credential}s in a list.
306 */
Robin Leeccaf9c92017-03-24 14:50:05 +0000307 private static class CredentialAdapter extends RecyclerView.Adapter<ViewHolder> {
Robin Leee68d9572016-07-19 16:10:39 +0100308 private static final int LAYOUT_RESOURCE = R.layout.user_credential_preference;
309
Robin Leeccaf9c92017-03-24 14:50:05 +0000310 private final List<Credential> mItems;
311 private final View.OnClickListener mListener;
312
313 public CredentialAdapter(List<Credential> items, @Nullable View.OnClickListener listener) {
314 mItems = items;
315 mListener = listener;
Robin Leebaefdcf2015-08-26 10:57:44 +0100316 }
317
318 @Override
Robin Leeccaf9c92017-03-24 14:50:05 +0000319 public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
320 final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
321 return new ViewHolder(inflater.inflate(LAYOUT_RESOURCE, parent, false));
322 }
323
324 @Override
325 public void onBindViewHolder(ViewHolder h, int position) {
326 getCredentialView(mItems.get(position), LAYOUT_RESOURCE, h.itemView, null, false);
327 h.itemView.setTag(mItems.get(position));
328 h.itemView.setOnClickListener(mListener);
329 }
330
331 @Override
332 public int getItemCount() {
333 return mItems.size();
334 }
335 }
336
337 private static class ViewHolder extends RecyclerView.ViewHolder {
338 public ViewHolder(View item) {
339 super(item);
Robin Leebaefdcf2015-08-26 10:57:44 +0100340 }
341 }
342
Robin Leee68d9572016-07-19 16:10:39 +0100343 /**
344 * Mapping from View IDs in {@link R} to the types of credentials they describe.
345 */
346 private static final SparseArray<Credential.Type> credentialViewTypes = new SparseArray<>();
347 static {
348 credentialViewTypes.put(R.id.contents_userkey, Credential.Type.USER_PRIVATE_KEY);
349 credentialViewTypes.put(R.id.contents_usercrt, Credential.Type.USER_CERTIFICATE);
350 credentialViewTypes.put(R.id.contents_cacrt, Credential.Type.CA_CERTIFICATE);
351 }
352
353 protected static View getCredentialView(Credential item, @LayoutRes int layoutResource,
354 @Nullable View view, ViewGroup parent, boolean expanded) {
355 if (view == null) {
356 view = LayoutInflater.from(parent.getContext()).inflate(layoutResource, parent, false);
357 }
358
359 ((TextView) view.findViewById(R.id.alias)).setText(item.alias);
360 ((TextView) view.findViewById(R.id.purpose)).setText(item.isSystem()
361 ? R.string.credential_for_vpn_and_apps
362 : R.string.credential_for_wifi);
363
364 view.findViewById(R.id.contents).setVisibility(expanded ? View.VISIBLE : View.GONE);
365 if (expanded) {
366 for (int i = 0; i < credentialViewTypes.size(); i++) {
367 final View detail = view.findViewById(credentialViewTypes.keyAt(i));
368 detail.setVisibility(item.storedTypes.contains(credentialViewTypes.valueAt(i))
369 ? View.VISIBLE : View.GONE);
370 }
371 }
372 return view;
373 }
374
375 static class AliasEntry {
376 public String alias;
377 public int uid;
378 }
379
Robin Leee2680422016-01-25 12:24:27 +0000380 static class Credential implements Parcelable {
381 static enum Type {
Robin Leebaefdcf2015-08-26 10:57:44 +0100382 CA_CERTIFICATE (Credentials.CA_CERTIFICATE),
383 USER_CERTIFICATE (Credentials.USER_CERTIFICATE),
384 USER_PRIVATE_KEY (Credentials.USER_PRIVATE_KEY),
385 USER_SECRET_KEY (Credentials.USER_SECRET_KEY);
386
387 final String prefix;
388
389 Type(String prefix) {
390 this.prefix = prefix;
391 }
392 }
393
394 /**
395 * Main part of the credential's alias. To fetch an item from KeyStore, prepend one of the
396 * prefixes from {@link CredentialItem.storedTypes}.
397 */
398 final String alias;
399
400 /**
Robin Leee68d9572016-07-19 16:10:39 +0100401 * UID under which this credential is stored. Typically {@link Process#SYSTEM_UID} but can
402 * also be {@link Process#WIFI_UID} for credentials installed as wifi certificates.
403 */
404 final int uid;
405
406 /**
Robin Leebaefdcf2015-08-26 10:57:44 +0100407 * Should contain some non-empty subset of:
408 * <ul>
409 * <li>{@link Credentials.CA_CERTIFICATE}</li>
410 * <li>{@link Credentials.USER_CERTIFICATE}</li>
411 * <li>{@link Credentials.USER_PRIVATE_KEY}</li>
412 * <li>{@link Credentials.USER_SECRET_KEY}</li>
413 * </ul>
414 */
Robin Lee04046a12016-01-19 11:42:57 +0000415 final EnumSet<Type> storedTypes = EnumSet.noneOf(Type.class);
Robin Leebaefdcf2015-08-26 10:57:44 +0100416
Robin Leee68d9572016-07-19 16:10:39 +0100417 Credential(final String alias, final int uid) {
Robin Leebaefdcf2015-08-26 10:57:44 +0100418 this.alias = alias;
Robin Leee68d9572016-07-19 16:10:39 +0100419 this.uid = uid;
Robin Leebaefdcf2015-08-26 10:57:44 +0100420 }
Robin Lee04046a12016-01-19 11:42:57 +0000421
422 Credential(Parcel in) {
Robin Leee68d9572016-07-19 16:10:39 +0100423 this(in.readString(), in.readInt());
Robin Lee04046a12016-01-19 11:42:57 +0000424
425 long typeBits = in.readLong();
426 for (Type i : Type.values()) {
427 if ((typeBits & (1L << i.ordinal())) != 0L) {
428 storedTypes.add(i);
429 }
430 }
431 }
432
433 public void writeToParcel(Parcel out, int flags) {
434 out.writeString(alias);
Robin Leee68d9572016-07-19 16:10:39 +0100435 out.writeInt(uid);
Robin Lee04046a12016-01-19 11:42:57 +0000436
437 long typeBits = 0;
438 for (Type i : storedTypes) {
439 typeBits |= 1L << i.ordinal();
440 }
441 out.writeLong(typeBits);
442 }
443
444 public int describeContents() {
445 return 0;
446 }
447
448 public static final Parcelable.Creator<Credential> CREATOR
449 = new Parcelable.Creator<Credential>() {
450 public Credential createFromParcel(Parcel in) {
451 return new Credential(in);
452 }
453
454 public Credential[] newArray(int size) {
455 return new Credential[size];
456 }
457 };
Robin Leee68d9572016-07-19 16:10:39 +0100458
459 public boolean isSystem() {
460 return UserHandle.getAppId(uid) == Process.SYSTEM_UID;
461 }
Robin Leebaefdcf2015-08-26 10:57:44 +0100462 }
463}