blob: 2681da6f214db53784285296bfe144f7f14437a4 [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
19import android.app.AlertDialog;
Robin Lee04046a12016-01-19 11:42:57 +000020import android.app.Dialog;
21import android.app.DialogFragment;
22import android.app.Fragment;
Robin Leebaefdcf2015-08-26 10:57:44 +010023import android.content.Context;
24import android.content.DialogInterface;
25import android.os.AsyncTask;
26import android.os.Bundle;
Robin Lee04046a12016-01-19 11:42:57 +000027import android.os.Parcel;
28import android.os.Parcelable;
Robin Leeda7bc512016-02-24 17:39:32 +000029import android.os.RemoteException;
Robin Leec421db72016-03-11 16:22:23 +000030import android.os.UserHandle;
31import android.os.UserManager;
Robin Leebaefdcf2015-08-26 10:57:44 +010032import android.security.Credentials;
Robin Leeda7bc512016-02-24 17:39:32 +000033import android.security.IKeyChainService;
34import android.security.KeyChain;
35import android.security.KeyChain.KeyChainConnection;
Robin Leebaefdcf2015-08-26 10:57:44 +010036import android.security.KeyStore;
Robin Leeda7bc512016-02-24 17:39:32 +000037import android.util.Log;
Robin Leebaefdcf2015-08-26 10:57:44 +010038import android.view.LayoutInflater;
39import android.view.View;
40import android.view.ViewGroup;
Robin Leebaefdcf2015-08-26 10:57:44 +010041import android.widget.AdapterView;
42import android.widget.AdapterView.OnItemClickListener;
43import android.widget.ArrayAdapter;
Robin Leebaefdcf2015-08-26 10:57:44 +010044import android.widget.ListView;
45import android.widget.TextView;
46
Robin Leeb70b3d82016-02-01 12:52:16 +000047import com.android.internal.logging.MetricsProto.MetricsEvent;
Ricky Wai95792742016-05-24 19:28:53 +010048import com.android.internal.widget.LockPatternUtils;
Robin Leec421db72016-03-11 16:22:23 +000049import com.android.settingslib.RestrictedLockUtils;
50import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
Robin Leeb70b3d82016-02-01 12:52:16 +000051
Robin Leebaefdcf2015-08-26 10:57:44 +010052import java.util.EnumSet;
Robin Leebaefdcf2015-08-26 10:57:44 +010053import java.util.SortedMap;
54import java.util.TreeMap;
55
Robin Leebaefdcf2015-08-26 10:57:44 +010056import static android.view.View.GONE;
Jason Monk39b46742015-09-10 15:52:51 -040057import static android.view.View.VISIBLE;
Robin Leebaefdcf2015-08-26 10:57:44 +010058
Udam Saini0708d9e2016-03-28 16:35:13 -070059public class UserCredentialsSettings extends OptionsMenuFragment implements OnItemClickListener {
Robin Leebaefdcf2015-08-26 10:57:44 +010060 private static final String TAG = "UserCredentialsSettings";
61
62 private View mRootView;
63 private ListView mListView;
64
65 @Override
66 protected int getMetricsCategory() {
Robin Leeb70b3d82016-02-01 12:52:16 +000067 return MetricsEvent.USER_CREDENTIALS;
Robin Leebaefdcf2015-08-26 10:57:44 +010068 }
69
70 @Override
71 public void onResume() {
72 super.onResume();
Robin Lee04046a12016-01-19 11:42:57 +000073 refreshItems();
Robin Leebaefdcf2015-08-26 10:57:44 +010074 }
75
76 @Override
77 public View onCreateView(
78 LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
79 mRootView = inflater.inflate(R.layout.user_credentials, parent, false);
80
81 // Set up an OnItemClickListener for the credential list.
82 mListView = (ListView) mRootView.findViewById(R.id.credential_list);
83 mListView.setOnItemClickListener(this);
84
85 return mRootView;
86 }
87
88 @Override
89 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
90 final Credential item = (Credential) parent.getItemAtPosition(position);
Robin Lee04046a12016-01-19 11:42:57 +000091 CredentialDialogFragment.show(this, item);
92 }
Robin Leebaefdcf2015-08-26 10:57:44 +010093
Robin Lee11fd5502016-05-16 15:42:34 +010094 protected void announceRemoval(String alias) {
95 if (isAdded()) {
96 mListView.announceForAccessibility(getString(R.string.user_credential_removed, alias));
97 }
98 }
99
Robin Lee04046a12016-01-19 11:42:57 +0000100 protected void refreshItems() {
101 if (isAdded()) {
102 new AliasLoader().execute();
103 }
104 }
Robin Leebaefdcf2015-08-26 10:57:44 +0100105
Robin Lee04046a12016-01-19 11:42:57 +0000106 public static class CredentialDialogFragment extends DialogFragment {
107 private static final String TAG = "CredentialDialogFragment";
108 private static final String ARG_CREDENTIAL = "credential";
109
110 public static void show(Fragment target, Credential item) {
111 final Bundle args = new Bundle();
112 args.putParcelable(ARG_CREDENTIAL, item);
113
Robin Leef8e2dbf2016-04-07 13:17:24 +0100114 if (target.getFragmentManager().findFragmentByTag(TAG) == null) {
115 final DialogFragment frag = new CredentialDialogFragment();
116 frag.setTargetFragment(target, /* requestCode */ -1);
117 frag.setArguments(args);
118 frag.show(target.getFragmentManager(), TAG);
119 }
Robin Lee04046a12016-01-19 11:42:57 +0000120 }
121
122 @Override
123 public Dialog onCreateDialog(Bundle savedInstanceState) {
124 final Credential item = (Credential) getArguments().getParcelable(ARG_CREDENTIAL);
125 View root = getActivity().getLayoutInflater()
126 .inflate(R.layout.user_credential_dialog, null);
127 ViewGroup infoContainer = (ViewGroup) root.findViewById(R.id.credential_container);
128 View view = new CredentialAdapter(getActivity(), R.layout.user_credential,
129 new Credential[] {item}).getView(0, null, null);
130 infoContainer.addView(view);
131
Robin Leec421db72016-03-11 16:22:23 +0000132 UserManager userManager
133 = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
134
135 AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
Robin Lee04046a12016-01-19 11:42:57 +0000136 .setView(root)
137 .setTitle(R.string.user_credential_title)
Robin Leec421db72016-03-11 16:22:23 +0000138 .setPositiveButton(R.string.done, null);
139
140 final String restriction = UserManager.DISALLOW_CONFIG_CREDENTIALS;
141 final int myUserId = UserHandle.myUserId();
142 if (!RestrictedLockUtils.hasBaseUserRestriction(getContext(), restriction, myUserId)) {
143 DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
144 @Override public void onClick(DialogInterface dialog, int id) {
145 final EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced(
146 getContext(), restriction, myUserId);
147 if (admin != null) {
148 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(),
149 admin);
150 } else {
Robin Lee11fd5502016-05-16 15:42:34 +0100151 new RemoveCredentialsTask(getTargetFragment()).execute(item.alias);
Robin Leec421db72016-03-11 16:22:23 +0000152 }
153 dialog.dismiss();
154 }
155 };
156 builder.setNegativeButton(R.string.trusted_credentials_remove_label, listener);
157 }
158 return builder.create();
Robin Lee04046a12016-01-19 11:42:57 +0000159 }
160
Robin Lee11fd5502016-05-16 15:42:34 +0100161 private class RemoveCredentialsTask extends AsyncTask<String, Void, String[]> {
Robin Leeda7bc512016-02-24 17:39:32 +0000162 private Fragment targetFragment;
163
Robin Lee11fd5502016-05-16 15:42:34 +0100164 public RemoveCredentialsTask(Fragment targetFragment) {
Robin Leeda7bc512016-02-24 17:39:32 +0000165 this.targetFragment = targetFragment;
Robin Lee04046a12016-01-19 11:42:57 +0000166 }
Robin Leeda7bc512016-02-24 17:39:32 +0000167
168 @Override
Robin Lee11fd5502016-05-16 15:42:34 +0100169 protected String[] doInBackground(String... aliases) {
Robin Leeda7bc512016-02-24 17:39:32 +0000170 try {
171 final KeyChainConnection conn = KeyChain.bind(getContext());
172 try {
173 IKeyChainService keyChain = conn.getService();
174 for (String alias : aliases) {
175 keyChain.removeKeyPair(alias);
176 }
177 } catch (RemoteException e) {
178 Log.w(TAG, "Removing credentials", e);
179 } finally {
180 conn.close();
181 }
182 } catch (InterruptedException e) {
183 Log.w(TAG, "Connecting to keychain", e);
184 }
Robin Lee11fd5502016-05-16 15:42:34 +0100185 return aliases;
Robin Leeda7bc512016-02-24 17:39:32 +0000186 }
187
188 @Override
Robin Lee11fd5502016-05-16 15:42:34 +0100189 protected void onPostExecute(String... aliases) {
190 if (targetFragment instanceof UserCredentialsSettings && targetFragment.isAdded()) {
191 final UserCredentialsSettings target = (UserCredentialsSettings) targetFragment;
192 for (final String alias : aliases) {
193 target.announceRemoval(alias);
194 }
195 target.refreshItems();
Robin Leeda7bc512016-02-24 17:39:32 +0000196 }
197 }
Robin Lee04046a12016-01-19 11:42:57 +0000198 }
Robin Leebaefdcf2015-08-26 10:57:44 +0100199 }
200
201 /**
202 * Opens a background connection to KeyStore to list user credentials.
203 * The credentials are stored in a {@link CredentialAdapter} attached to the main
204 * {@link ListView} in the fragment.
205 */
Robin Lee04046a12016-01-19 11:42:57 +0000206 private class AliasLoader extends AsyncTask<Void, Void, SortedMap<String, Credential>> {
Robin Leebaefdcf2015-08-26 10:57:44 +0100207 @Override
Robin Lee04046a12016-01-19 11:42:57 +0000208 protected SortedMap<String, Credential> doInBackground(Void... params) {
Robin Leebaefdcf2015-08-26 10:57:44 +0100209 // Create a list of names for credential sets, ordered by name.
210 SortedMap<String, Credential> credentials = new TreeMap<>();
211 KeyStore keyStore = KeyStore.getInstance();
212 for (final Credential.Type type : Credential.Type.values()) {
213 for (final String alias : keyStore.list(type.prefix)) {
Ricky Wai95792742016-05-24 19:28:53 +0100214 // Do not show work profile keys in user credentials
215 if (alias.startsWith(LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT) ||
216 alias.startsWith(LockPatternUtils.PROFILE_KEY_NAME_DECRYPT)) {
217 continue;
218 }
Robin Leebaefdcf2015-08-26 10:57:44 +0100219 Credential c = credentials.get(alias);
220 if (c == null) {
221 credentials.put(alias, (c = new Credential(alias)));
222 }
223 c.storedTypes.add(type);
224 }
225 }
Robin Lee04046a12016-01-19 11:42:57 +0000226 return credentials;
Robin Leebaefdcf2015-08-26 10:57:44 +0100227 }
228
229 @Override
Robin Lee04046a12016-01-19 11:42:57 +0000230 protected void onPostExecute(SortedMap<String, Credential> credentials) {
231 // Convert the results to an array and present them using an ArrayAdapter.
232 mListView.setAdapter(new CredentialAdapter(getContext(), R.layout.user_credential,
233 credentials.values().toArray(new Credential[0])));
Robin Leebaefdcf2015-08-26 10:57:44 +0100234 }
235 }
236
237 /**
238 * Helper class to display {@link Credential}s in a list.
239 */
240 private static class CredentialAdapter extends ArrayAdapter<Credential> {
241 public CredentialAdapter(Context context, int resource, Credential[] objects) {
242 super(context, resource, objects);
243 }
244
245 @Override
246 public View getView(int position, View view, ViewGroup parent) {
247 if (view == null) {
248 view = LayoutInflater.from(getContext())
249 .inflate(R.layout.user_credential, parent, false);
250 }
251 Credential item = getItem(position);
252 ((TextView) view.findViewById(R.id.alias)).setText(item.alias);
253 view.findViewById(R.id.contents_userkey).setVisibility(
254 item.storedTypes.contains(Credential.Type.USER_PRIVATE_KEY) ? VISIBLE : GONE);
255 view.findViewById(R.id.contents_usercrt).setVisibility(
256 item.storedTypes.contains(Credential.Type.USER_CERTIFICATE) ? VISIBLE : GONE);
257 view.findViewById(R.id.contents_cacrt).setVisibility(
258 item.storedTypes.contains(Credential.Type.CA_CERTIFICATE) ? VISIBLE : GONE);
259 return view;
260 }
261 }
262
Robin Leee2680422016-01-25 12:24:27 +0000263 static class Credential implements Parcelable {
264 static enum Type {
Robin Leebaefdcf2015-08-26 10:57:44 +0100265 CA_CERTIFICATE (Credentials.CA_CERTIFICATE),
266 USER_CERTIFICATE (Credentials.USER_CERTIFICATE),
267 USER_PRIVATE_KEY (Credentials.USER_PRIVATE_KEY),
268 USER_SECRET_KEY (Credentials.USER_SECRET_KEY);
269
270 final String prefix;
271
272 Type(String prefix) {
273 this.prefix = prefix;
274 }
275 }
276
277 /**
278 * Main part of the credential's alias. To fetch an item from KeyStore, prepend one of the
279 * prefixes from {@link CredentialItem.storedTypes}.
280 */
281 final String alias;
282
283 /**
284 * Should contain some non-empty subset of:
285 * <ul>
286 * <li>{@link Credentials.CA_CERTIFICATE}</li>
287 * <li>{@link Credentials.USER_CERTIFICATE}</li>
288 * <li>{@link Credentials.USER_PRIVATE_KEY}</li>
289 * <li>{@link Credentials.USER_SECRET_KEY}</li>
290 * </ul>
291 */
Robin Lee04046a12016-01-19 11:42:57 +0000292 final EnumSet<Type> storedTypes = EnumSet.noneOf(Type.class);
Robin Leebaefdcf2015-08-26 10:57:44 +0100293
294 Credential(final String alias) {
295 this.alias = alias;
296 }
Robin Lee04046a12016-01-19 11:42:57 +0000297
298 Credential(Parcel in) {
299 this(in.readString());
300
301 long typeBits = in.readLong();
302 for (Type i : Type.values()) {
303 if ((typeBits & (1L << i.ordinal())) != 0L) {
304 storedTypes.add(i);
305 }
306 }
307 }
308
309 public void writeToParcel(Parcel out, int flags) {
310 out.writeString(alias);
311
312 long typeBits = 0;
313 for (Type i : storedTypes) {
314 typeBits |= 1L << i.ordinal();
315 }
316 out.writeLong(typeBits);
317 }
318
319 public int describeContents() {
320 return 0;
321 }
322
323 public static final Parcelable.Creator<Credential> CREATOR
324 = new Parcelable.Creator<Credential>() {
325 public Credential createFromParcel(Parcel in) {
326 return new Credential(in);
327 }
328
329 public Credential[] newArray(int size) {
330 return new Credential[size];
331 }
332 };
Robin Leebaefdcf2015-08-26 10:57:44 +0100333 }
334}