blob: ea9eee9eb13de730bc3a17cfe24e6f8fb1ba50ea [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;
Robin Leebaefdcf2015-08-26 10:57:44 +010021import android.app.AlertDialog;
Robin Lee04046a12016-01-19 11:42:57 +000022import android.app.Dialog;
23import android.app.DialogFragment;
24import android.app.Fragment;
Robin Leebaefdcf2015-08-26 10:57:44 +010025import android.content.Context;
26import android.content.DialogInterface;
27import android.os.AsyncTask;
28import android.os.Bundle;
Robin Lee04046a12016-01-19 11:42:57 +000029import android.os.Parcel;
30import android.os.Parcelable;
Robin Leee68d9572016-07-19 16:10:39 +010031import android.os.Process;
Robin Leeda7bc512016-02-24 17:39:32 +000032import android.os.RemoteException;
Robin Leec421db72016-03-11 16:22:23 +000033import android.os.UserHandle;
34import android.os.UserManager;
Robin Leebaefdcf2015-08-26 10:57:44 +010035import android.security.Credentials;
Robin Leeda7bc512016-02-24 17:39:32 +000036import android.security.IKeyChainService;
37import android.security.KeyChain;
38import android.security.KeyChain.KeyChainConnection;
Robin Leebaefdcf2015-08-26 10:57:44 +010039import android.security.KeyStore;
Robin Leeda7bc512016-02-24 17:39:32 +000040import android.util.Log;
Robin Leee68d9572016-07-19 16:10:39 +010041import android.util.SparseArray;
Robin Leebaefdcf2015-08-26 10:57:44 +010042import android.view.LayoutInflater;
43import android.view.View;
44import android.view.ViewGroup;
Robin Leebaefdcf2015-08-26 10:57:44 +010045import android.widget.AdapterView;
46import android.widget.AdapterView.OnItemClickListener;
47import android.widget.ArrayAdapter;
Robin Leebaefdcf2015-08-26 10:57:44 +010048import android.widget.ListView;
49import android.widget.TextView;
50
Tamas Berghammer265d3c22016-06-22 15:34:45 +010051import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
Ricky Wai95792742016-05-24 19:28:53 +010052import com.android.internal.widget.LockPatternUtils;
Fan Zhang1e516282016-09-16 12:45:07 -070053import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
Robin Leec421db72016-03-11 16:22:23 +000054import com.android.settingslib.RestrictedLockUtils;
55import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
Robin Leeb70b3d82016-02-01 12:52:16 +000056
Robin Leee68d9572016-07-19 16:10:39 +010057import java.util.ArrayList;
Robin Leebaefdcf2015-08-26 10:57:44 +010058import java.util.EnumSet;
Robin Leee68d9572016-07-19 16:10:39 +010059import java.util.List;
Robin Leebaefdcf2015-08-26 10:57:44 +010060import java.util.SortedMap;
61import java.util.TreeMap;
62
Robin Leebaefdcf2015-08-26 10:57:44 +010063import static android.view.View.GONE;
Jason Monk39b46742015-09-10 15:52:51 -040064import static android.view.View.VISIBLE;
Robin Leebaefdcf2015-08-26 10:57:44 +010065
Udam Saini0708d9e2016-03-28 16:35:13 -070066public class UserCredentialsSettings extends OptionsMenuFragment implements OnItemClickListener {
Robin Leebaefdcf2015-08-26 10:57:44 +010067 private static final String TAG = "UserCredentialsSettings";
68
Robin Leebaefdcf2015-08-26 10:57:44 +010069 private ListView mListView;
70
71 @Override
Fan Zhang65076132016-08-08 10:25:13 -070072 public int getMetricsCategory() {
Robin Leeb70b3d82016-02-01 12:52:16 +000073 return MetricsEvent.USER_CREDENTIALS;
Robin Leebaefdcf2015-08-26 10:57:44 +010074 }
75
76 @Override
77 public void onResume() {
78 super.onResume();
Robin Lee04046a12016-01-19 11:42:57 +000079 refreshItems();
Robin Leebaefdcf2015-08-26 10:57:44 +010080 }
81
82 @Override
83 public View onCreateView(
84 LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
Robin Leee68d9572016-07-19 16:10:39 +010085 final View rootView = inflater.inflate(R.layout.user_credentials, parent, false);
Robin Leebaefdcf2015-08-26 10:57:44 +010086
87 // Set up an OnItemClickListener for the credential list.
Robin Leee68d9572016-07-19 16:10:39 +010088 mListView = (ListView) rootView.findViewById(R.id.credential_list);
Robin Leebaefdcf2015-08-26 10:57:44 +010089 mListView.setOnItemClickListener(this);
90
Robin Leee68d9572016-07-19 16:10:39 +010091 return rootView;
Robin Leebaefdcf2015-08-26 10:57:44 +010092 }
93
94 @Override
95 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
96 final Credential item = (Credential) parent.getItemAtPosition(position);
Robin Lee04046a12016-01-19 11:42:57 +000097 CredentialDialogFragment.show(this, item);
98 }
Robin Leebaefdcf2015-08-26 10:57:44 +010099
Robin Lee11fd5502016-05-16 15:42:34 +0100100 protected void announceRemoval(String alias) {
101 if (isAdded()) {
102 mListView.announceForAccessibility(getString(R.string.user_credential_removed, alias));
103 }
104 }
105
Robin Lee04046a12016-01-19 11:42:57 +0000106 protected void refreshItems() {
107 if (isAdded()) {
108 new AliasLoader().execute();
109 }
110 }
Robin Leebaefdcf2015-08-26 10:57:44 +0100111
Fan Zhang1e516282016-09-16 12:45:07 -0700112 public static class CredentialDialogFragment extends InstrumentedDialogFragment {
Robin Lee04046a12016-01-19 11:42:57 +0000113 private static final String TAG = "CredentialDialogFragment";
114 private static final String ARG_CREDENTIAL = "credential";
115
116 public static void show(Fragment target, Credential item) {
117 final Bundle args = new Bundle();
118 args.putParcelable(ARG_CREDENTIAL, item);
119
Robin Leef8e2dbf2016-04-07 13:17:24 +0100120 if (target.getFragmentManager().findFragmentByTag(TAG) == null) {
121 final DialogFragment frag = new CredentialDialogFragment();
122 frag.setTargetFragment(target, /* requestCode */ -1);
123 frag.setArguments(args);
124 frag.show(target.getFragmentManager(), TAG);
125 }
Robin Lee04046a12016-01-19 11:42:57 +0000126 }
127
128 @Override
129 public Dialog onCreateDialog(Bundle savedInstanceState) {
130 final Credential item = (Credential) getArguments().getParcelable(ARG_CREDENTIAL);
Robin Leee68d9572016-07-19 16:10:39 +0100131
Robin Lee04046a12016-01-19 11:42:57 +0000132 View root = getActivity().getLayoutInflater()
133 .inflate(R.layout.user_credential_dialog, null);
134 ViewGroup infoContainer = (ViewGroup) root.findViewById(R.id.credential_container);
Robin Leee68d9572016-07-19 16:10:39 +0100135 View contentView = getCredentialView(item, R.layout.user_credential, null,
136 infoContainer, /* expanded */ true);
137 infoContainer.addView(contentView);
Robin Leec421db72016-03-11 16:22:23 +0000138
139 AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
Robin Lee04046a12016-01-19 11:42:57 +0000140 .setView(root)
141 .setTitle(R.string.user_credential_title)
Robin Leec421db72016-03-11 16:22:23 +0000142 .setPositiveButton(R.string.done, null);
143
144 final String restriction = UserManager.DISALLOW_CONFIG_CREDENTIALS;
145 final int myUserId = UserHandle.myUserId();
146 if (!RestrictedLockUtils.hasBaseUserRestriction(getContext(), restriction, myUserId)) {
147 DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
148 @Override public void onClick(DialogInterface dialog, int id) {
149 final EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced(
150 getContext(), restriction, myUserId);
151 if (admin != null) {
152 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(),
153 admin);
154 } else {
Robin Leee68d9572016-07-19 16:10:39 +0100155 new RemoveCredentialsTask(getContext(), getTargetFragment())
156 .execute(item);
Robin Leec421db72016-03-11 16:22:23 +0000157 }
158 dialog.dismiss();
159 }
160 };
Robin Leee68d9572016-07-19 16:10:39 +0100161 if (item.isSystem()) {
162 // TODO: a safe means of clearing wifi certificates. Configs refer to aliases
163 // directly so deleting certs will break dependent access points.
164 builder.setNegativeButton(R.string.trusted_credentials_remove_label, listener);
165 }
Robin Leec421db72016-03-11 16:22:23 +0000166 }
167 return builder.create();
Robin Lee04046a12016-01-19 11:42:57 +0000168 }
169
Fan Zhang1e516282016-09-16 12:45:07 -0700170 @Override
171 public int getMetricsCategory() {
172 return MetricsEvent.DIALOG_USER_CREDENTIAL;
173 }
174
Robin Leee68d9572016-07-19 16:10:39 +0100175 /**
176 * Deletes all certificates and keys under a given alias.
177 *
178 * If the {@link Credential} is for a system alias, all active grants to the alias will be
179 * removed using {@link KeyChain}.
180 */
181 private class RemoveCredentialsTask extends AsyncTask<Credential, Void, Credential[]> {
182 private Context context;
Robin Leeda7bc512016-02-24 17:39:32 +0000183 private Fragment targetFragment;
184
Robin Leee68d9572016-07-19 16:10:39 +0100185 public RemoveCredentialsTask(Context context, Fragment targetFragment) {
186 this.context = context;
Robin Leeda7bc512016-02-24 17:39:32 +0000187 this.targetFragment = targetFragment;
Robin Lee04046a12016-01-19 11:42:57 +0000188 }
Robin Leeda7bc512016-02-24 17:39:32 +0000189
190 @Override
Robin Leee68d9572016-07-19 16:10:39 +0100191 protected Credential[] doInBackground(Credential... credentials) {
192 for (final Credential credential : credentials) {
193 if (credential.isSystem()) {
194 removeGrantsAndDelete(credential);
195 continue;
Robin Leeda7bc512016-02-24 17:39:32 +0000196 }
Robin Leee68d9572016-07-19 16:10:39 +0100197 throw new UnsupportedOperationException(
198 "Not implemented for wifi certificates. This should not be reachable.");
Robin Leeda7bc512016-02-24 17:39:32 +0000199 }
Robin Leee68d9572016-07-19 16:10:39 +0100200 return credentials;
201 }
202
203 private void removeGrantsAndDelete(final Credential credential) {
204 final KeyChainConnection conn;
205 try {
206 conn = KeyChain.bind(getContext());
207 } catch (InterruptedException e) {
208 Log.w(TAG, "Connecting to KeyChain", e);
209 return;
210 }
211
212 try {
213 IKeyChainService keyChain = conn.getService();
214 keyChain.removeKeyPair(credential.alias);
215 } catch (RemoteException e) {
216 Log.w(TAG, "Removing credentials", e);
217 } finally {
218 conn.close();
219 }
Robin Leeda7bc512016-02-24 17:39:32 +0000220 }
221
222 @Override
Robin Leee68d9572016-07-19 16:10:39 +0100223 protected void onPostExecute(Credential... credentials) {
Robin Lee11fd5502016-05-16 15:42:34 +0100224 if (targetFragment instanceof UserCredentialsSettings && targetFragment.isAdded()) {
225 final UserCredentialsSettings target = (UserCredentialsSettings) targetFragment;
Robin Leee68d9572016-07-19 16:10:39 +0100226 for (final Credential credential : credentials) {
227 target.announceRemoval(credential.alias);
Robin Lee11fd5502016-05-16 15:42:34 +0100228 }
229 target.refreshItems();
Robin Leeda7bc512016-02-24 17:39:32 +0000230 }
231 }
Robin Lee04046a12016-01-19 11:42:57 +0000232 }
Robin Leebaefdcf2015-08-26 10:57:44 +0100233 }
234
235 /**
236 * Opens a background connection to KeyStore to list user credentials.
237 * The credentials are stored in a {@link CredentialAdapter} attached to the main
238 * {@link ListView} in the fragment.
239 */
Robin Leee68d9572016-07-19 16:10:39 +0100240 private class AliasLoader extends AsyncTask<Void, Void, List<Credential>> {
241 /**
242 * @return a list of credentials ordered:
243 * <ol>
244 * <li>first by purpose;</li>
245 * <li>then by alias.</li>
246 * </ol>
247 */
Robin Leebaefdcf2015-08-26 10:57:44 +0100248 @Override
Robin Leee68d9572016-07-19 16:10:39 +0100249 protected List<Credential> doInBackground(Void... params) {
250 final KeyStore keyStore = KeyStore.getInstance();
251
252 // Certificates can be installed into SYSTEM_UID or WIFI_UID through CertInstaller.
253 final int myUserId = UserHandle.myUserId();
254 final int systemUid = UserHandle.getUid(myUserId, Process.SYSTEM_UID);
255 final int wifiUid = UserHandle.getUid(myUserId, Process.WIFI_UID);
256
257 List<Credential> credentials = new ArrayList<>();
258 credentials.addAll(getCredentialsForUid(keyStore, systemUid).values());
259 credentials.addAll(getCredentialsForUid(keyStore, wifiUid).values());
260 return credentials;
261 }
262
263 private SortedMap<String, Credential> getCredentialsForUid(KeyStore keyStore, int uid) {
264 final SortedMap<String, Credential> aliasMap = new TreeMap<>();
Robin Leebaefdcf2015-08-26 10:57:44 +0100265 for (final Credential.Type type : Credential.Type.values()) {
Robin Leee68d9572016-07-19 16:10:39 +0100266 for (final String alias : keyStore.list(type.prefix, uid)) {
Rubin Xu52221d82017-02-09 11:09:11 +0000267 if (UserHandle.getAppId(uid) == Process.SYSTEM_UID) {
268 // Do not show work profile keys in user credentials
269 if (alias.startsWith(LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT) ||
270 alias.startsWith(LockPatternUtils.PROFILE_KEY_NAME_DECRYPT)) {
271 continue;
272 }
273 // Do not show synthetic password keys in user credential
274 if (alias.startsWith(LockPatternUtils.SYNTHETIC_PASSWORD_KEY_PREFIX)) {
275 continue;
276 }
Ricky Wai95792742016-05-24 19:28:53 +0100277 }
Robin Leee68d9572016-07-19 16:10:39 +0100278 Credential c = aliasMap.get(alias);
Robin Leebaefdcf2015-08-26 10:57:44 +0100279 if (c == null) {
Robin Leee68d9572016-07-19 16:10:39 +0100280 c = new Credential(alias, uid);
281 aliasMap.put(alias, c);
Robin Leebaefdcf2015-08-26 10:57:44 +0100282 }
283 c.storedTypes.add(type);
284 }
285 }
Robin Leee68d9572016-07-19 16:10:39 +0100286 return aliasMap;
Robin Leebaefdcf2015-08-26 10:57:44 +0100287 }
288
289 @Override
Robin Leee68d9572016-07-19 16:10:39 +0100290 protected void onPostExecute(List<Credential> credentials) {
291 final Credential[] credentialArray = credentials.toArray(new Credential[0]);
292 mListView.setAdapter(new CredentialAdapter(getContext(), credentialArray));
Robin Leebaefdcf2015-08-26 10:57:44 +0100293 }
294 }
295
296 /**
297 * Helper class to display {@link Credential}s in a list.
298 */
299 private static class CredentialAdapter extends ArrayAdapter<Credential> {
Robin Leee68d9572016-07-19 16:10:39 +0100300 private static final int LAYOUT_RESOURCE = R.layout.user_credential_preference;
301
302 public CredentialAdapter(Context context, final Credential[] objects) {
303 super(context, LAYOUT_RESOURCE, objects);
Robin Leebaefdcf2015-08-26 10:57:44 +0100304 }
305
306 @Override
Robin Leee68d9572016-07-19 16:10:39 +0100307 public View getView(int position, @Nullable View view, ViewGroup parent) {
308 return getCredentialView(getItem(position), LAYOUT_RESOURCE, view, parent,
309 /* expanded */ false);
Robin Leebaefdcf2015-08-26 10:57:44 +0100310 }
311 }
312
Robin Leee68d9572016-07-19 16:10:39 +0100313 /**
314 * Mapping from View IDs in {@link R} to the types of credentials they describe.
315 */
316 private static final SparseArray<Credential.Type> credentialViewTypes = new SparseArray<>();
317 static {
318 credentialViewTypes.put(R.id.contents_userkey, Credential.Type.USER_PRIVATE_KEY);
319 credentialViewTypes.put(R.id.contents_usercrt, Credential.Type.USER_CERTIFICATE);
320 credentialViewTypes.put(R.id.contents_cacrt, Credential.Type.CA_CERTIFICATE);
321 }
322
323 protected static View getCredentialView(Credential item, @LayoutRes int layoutResource,
324 @Nullable View view, ViewGroup parent, boolean expanded) {
325 if (view == null) {
326 view = LayoutInflater.from(parent.getContext()).inflate(layoutResource, parent, false);
327 }
328
329 ((TextView) view.findViewById(R.id.alias)).setText(item.alias);
330 ((TextView) view.findViewById(R.id.purpose)).setText(item.isSystem()
331 ? R.string.credential_for_vpn_and_apps
332 : R.string.credential_for_wifi);
333
334 view.findViewById(R.id.contents).setVisibility(expanded ? View.VISIBLE : View.GONE);
335 if (expanded) {
336 for (int i = 0; i < credentialViewTypes.size(); i++) {
337 final View detail = view.findViewById(credentialViewTypes.keyAt(i));
338 detail.setVisibility(item.storedTypes.contains(credentialViewTypes.valueAt(i))
339 ? View.VISIBLE : View.GONE);
340 }
341 }
342 return view;
343 }
344
345 static class AliasEntry {
346 public String alias;
347 public int uid;
348 }
349
Robin Leee2680422016-01-25 12:24:27 +0000350 static class Credential implements Parcelable {
351 static enum Type {
Robin Leebaefdcf2015-08-26 10:57:44 +0100352 CA_CERTIFICATE (Credentials.CA_CERTIFICATE),
353 USER_CERTIFICATE (Credentials.USER_CERTIFICATE),
354 USER_PRIVATE_KEY (Credentials.USER_PRIVATE_KEY),
355 USER_SECRET_KEY (Credentials.USER_SECRET_KEY);
356
357 final String prefix;
358
359 Type(String prefix) {
360 this.prefix = prefix;
361 }
362 }
363
364 /**
365 * Main part of the credential's alias. To fetch an item from KeyStore, prepend one of the
366 * prefixes from {@link CredentialItem.storedTypes}.
367 */
368 final String alias;
369
370 /**
Robin Leee68d9572016-07-19 16:10:39 +0100371 * UID under which this credential is stored. Typically {@link Process#SYSTEM_UID} but can
372 * also be {@link Process#WIFI_UID} for credentials installed as wifi certificates.
373 */
374 final int uid;
375
376 /**
Robin Leebaefdcf2015-08-26 10:57:44 +0100377 * Should contain some non-empty subset of:
378 * <ul>
379 * <li>{@link Credentials.CA_CERTIFICATE}</li>
380 * <li>{@link Credentials.USER_CERTIFICATE}</li>
381 * <li>{@link Credentials.USER_PRIVATE_KEY}</li>
382 * <li>{@link Credentials.USER_SECRET_KEY}</li>
383 * </ul>
384 */
Robin Lee04046a12016-01-19 11:42:57 +0000385 final EnumSet<Type> storedTypes = EnumSet.noneOf(Type.class);
Robin Leebaefdcf2015-08-26 10:57:44 +0100386
Robin Leee68d9572016-07-19 16:10:39 +0100387 Credential(final String alias, final int uid) {
Robin Leebaefdcf2015-08-26 10:57:44 +0100388 this.alias = alias;
Robin Leee68d9572016-07-19 16:10:39 +0100389 this.uid = uid;
Robin Leebaefdcf2015-08-26 10:57:44 +0100390 }
Robin Lee04046a12016-01-19 11:42:57 +0000391
392 Credential(Parcel in) {
Robin Leee68d9572016-07-19 16:10:39 +0100393 this(in.readString(), in.readInt());
Robin Lee04046a12016-01-19 11:42:57 +0000394
395 long typeBits = in.readLong();
396 for (Type i : Type.values()) {
397 if ((typeBits & (1L << i.ordinal())) != 0L) {
398 storedTypes.add(i);
399 }
400 }
401 }
402
403 public void writeToParcel(Parcel out, int flags) {
404 out.writeString(alias);
Robin Leee68d9572016-07-19 16:10:39 +0100405 out.writeInt(uid);
Robin Lee04046a12016-01-19 11:42:57 +0000406
407 long typeBits = 0;
408 for (Type i : storedTypes) {
409 typeBits |= 1L << i.ordinal();
410 }
411 out.writeLong(typeBits);
412 }
413
414 public int describeContents() {
415 return 0;
416 }
417
418 public static final Parcelable.Creator<Credential> CREATOR
419 = new Parcelable.Creator<Credential>() {
420 public Credential createFromParcel(Parcel in) {
421 return new Credential(in);
422 }
423
424 public Credential[] newArray(int size) {
425 return new Credential[size];
426 }
427 };
Robin Leee68d9572016-07-19 16:10:39 +0100428
429 public boolean isSystem() {
430 return UserHandle.getAppId(uid) == Process.SYSTEM_UID;
431 }
Robin Leebaefdcf2015-08-26 10:57:44 +0100432 }
433}