blob: c264ff7318d04a84ad4acf4877d8b45c439a7c70 [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 Lee04046a12016-01-19 11:42:57 +000094 protected void refreshItems() {
95 if (isAdded()) {
96 new AliasLoader().execute();
97 }
98 }
Robin Leebaefdcf2015-08-26 10:57:44 +010099
Robin Lee04046a12016-01-19 11:42:57 +0000100 public static class CredentialDialogFragment extends DialogFragment {
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
Robin Leef8e2dbf2016-04-07 13:17:24 +0100108 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 }
Robin Lee04046a12016-01-19 11:42:57 +0000114 }
115
116 @Override
117 public Dialog onCreateDialog(Bundle savedInstanceState) {
118 final Credential item = (Credential) getArguments().getParcelable(ARG_CREDENTIAL);
119 View root = getActivity().getLayoutInflater()
120 .inflate(R.layout.user_credential_dialog, null);
121 ViewGroup infoContainer = (ViewGroup) root.findViewById(R.id.credential_container);
122 View view = new CredentialAdapter(getActivity(), R.layout.user_credential,
123 new Credential[] {item}).getView(0, null, null);
124 infoContainer.addView(view);
125
Robin Leec421db72016-03-11 16:22:23 +0000126 UserManager userManager
127 = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
128
129 AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
Robin Lee04046a12016-01-19 11:42:57 +0000130 .setView(root)
131 .setTitle(R.string.user_credential_title)
Robin Leec421db72016-03-11 16:22:23 +0000132 .setPositiveButton(R.string.done, null);
133
134 final String restriction = UserManager.DISALLOW_CONFIG_CREDENTIALS;
135 final int myUserId = UserHandle.myUserId();
136 if (!RestrictedLockUtils.hasBaseUserRestriction(getContext(), restriction, myUserId)) {
137 DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
138 @Override public void onClick(DialogInterface dialog, int id) {
139 final EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced(
140 getContext(), restriction, myUserId);
141 if (admin != null) {
142 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(),
143 admin);
144 } else {
145 new RemoveCredentialsTask(getContext(), getTargetFragment())
146 .execute(item.alias);
147 }
148 dialog.dismiss();
149 }
150 };
151 builder.setNegativeButton(R.string.trusted_credentials_remove_label, listener);
152 }
153 return builder.create();
Robin Lee04046a12016-01-19 11:42:57 +0000154 }
155
Robin Leeda7bc512016-02-24 17:39:32 +0000156 private class RemoveCredentialsTask extends AsyncTask<String, Void, Void> {
157 private Context context;
158 private Fragment targetFragment;
159
160 public RemoveCredentialsTask(Context context, Fragment targetFragment) {
161 this.context = context;
162 this.targetFragment = targetFragment;
Robin Lee04046a12016-01-19 11:42:57 +0000163 }
Robin Leeda7bc512016-02-24 17:39:32 +0000164
165 @Override
166 protected Void doInBackground(String... aliases) {
167 try {
168 final KeyChainConnection conn = KeyChain.bind(getContext());
169 try {
170 IKeyChainService keyChain = conn.getService();
171 for (String alias : aliases) {
172 keyChain.removeKeyPair(alias);
173 }
174 } catch (RemoteException e) {
175 Log.w(TAG, "Removing credentials", e);
176 } finally {
177 conn.close();
178 }
179 } catch (InterruptedException e) {
180 Log.w(TAG, "Connecting to keychain", e);
181 }
182 return null;
183 }
184
185 @Override
186 protected void onPostExecute(Void result) {
187 if (targetFragment instanceof UserCredentialsSettings) {
188 ((UserCredentialsSettings) targetFragment).refreshItems();
189 }
190 }
Robin Lee04046a12016-01-19 11:42:57 +0000191 }
Robin Leebaefdcf2015-08-26 10:57:44 +0100192 }
193
194 /**
195 * Opens a background connection to KeyStore to list user credentials.
196 * The credentials are stored in a {@link CredentialAdapter} attached to the main
197 * {@link ListView} in the fragment.
198 */
Robin Lee04046a12016-01-19 11:42:57 +0000199 private class AliasLoader extends AsyncTask<Void, Void, SortedMap<String, Credential>> {
Robin Leebaefdcf2015-08-26 10:57:44 +0100200 @Override
Robin Lee04046a12016-01-19 11:42:57 +0000201 protected SortedMap<String, Credential> doInBackground(Void... params) {
Robin Leebaefdcf2015-08-26 10:57:44 +0100202 // Create a list of names for credential sets, ordered by name.
203 SortedMap<String, Credential> credentials = new TreeMap<>();
204 KeyStore keyStore = KeyStore.getInstance();
205 for (final Credential.Type type : Credential.Type.values()) {
206 for (final String alias : keyStore.list(type.prefix)) {
Ricky Wai95792742016-05-24 19:28:53 +0100207 // Do not show work profile keys in user credentials
208 if (alias.startsWith(LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT) ||
209 alias.startsWith(LockPatternUtils.PROFILE_KEY_NAME_DECRYPT)) {
210 continue;
211 }
Robin Leebaefdcf2015-08-26 10:57:44 +0100212 Credential c = credentials.get(alias);
213 if (c == null) {
214 credentials.put(alias, (c = new Credential(alias)));
215 }
216 c.storedTypes.add(type);
217 }
218 }
Robin Lee04046a12016-01-19 11:42:57 +0000219 return credentials;
Robin Leebaefdcf2015-08-26 10:57:44 +0100220 }
221
222 @Override
Robin Lee04046a12016-01-19 11:42:57 +0000223 protected void onPostExecute(SortedMap<String, Credential> credentials) {
224 // Convert the results to an array and present them using an ArrayAdapter.
225 mListView.setAdapter(new CredentialAdapter(getContext(), R.layout.user_credential,
226 credentials.values().toArray(new Credential[0])));
Robin Leebaefdcf2015-08-26 10:57:44 +0100227 }
228 }
229
230 /**
231 * Helper class to display {@link Credential}s in a list.
232 */
233 private static class CredentialAdapter extends ArrayAdapter<Credential> {
234 public CredentialAdapter(Context context, int resource, Credential[] objects) {
235 super(context, resource, objects);
236 }
237
238 @Override
239 public View getView(int position, View view, ViewGroup parent) {
240 if (view == null) {
241 view = LayoutInflater.from(getContext())
242 .inflate(R.layout.user_credential, parent, false);
243 }
244 Credential item = getItem(position);
245 ((TextView) view.findViewById(R.id.alias)).setText(item.alias);
246 view.findViewById(R.id.contents_userkey).setVisibility(
247 item.storedTypes.contains(Credential.Type.USER_PRIVATE_KEY) ? VISIBLE : GONE);
248 view.findViewById(R.id.contents_usercrt).setVisibility(
249 item.storedTypes.contains(Credential.Type.USER_CERTIFICATE) ? VISIBLE : GONE);
250 view.findViewById(R.id.contents_cacrt).setVisibility(
251 item.storedTypes.contains(Credential.Type.CA_CERTIFICATE) ? VISIBLE : GONE);
252 return view;
253 }
254 }
255
Robin Leee2680422016-01-25 12:24:27 +0000256 static class Credential implements Parcelable {
257 static enum Type {
Robin Leebaefdcf2015-08-26 10:57:44 +0100258 CA_CERTIFICATE (Credentials.CA_CERTIFICATE),
259 USER_CERTIFICATE (Credentials.USER_CERTIFICATE),
260 USER_PRIVATE_KEY (Credentials.USER_PRIVATE_KEY),
261 USER_SECRET_KEY (Credentials.USER_SECRET_KEY);
262
263 final String prefix;
264
265 Type(String prefix) {
266 this.prefix = prefix;
267 }
268 }
269
270 /**
271 * Main part of the credential's alias. To fetch an item from KeyStore, prepend one of the
272 * prefixes from {@link CredentialItem.storedTypes}.
273 */
274 final String alias;
275
276 /**
277 * Should contain some non-empty subset of:
278 * <ul>
279 * <li>{@link Credentials.CA_CERTIFICATE}</li>
280 * <li>{@link Credentials.USER_CERTIFICATE}</li>
281 * <li>{@link Credentials.USER_PRIVATE_KEY}</li>
282 * <li>{@link Credentials.USER_SECRET_KEY}</li>
283 * </ul>
284 */
Robin Lee04046a12016-01-19 11:42:57 +0000285 final EnumSet<Type> storedTypes = EnumSet.noneOf(Type.class);
Robin Leebaefdcf2015-08-26 10:57:44 +0100286
287 Credential(final String alias) {
288 this.alias = alias;
289 }
Robin Lee04046a12016-01-19 11:42:57 +0000290
291 Credential(Parcel in) {
292 this(in.readString());
293
294 long typeBits = in.readLong();
295 for (Type i : Type.values()) {
296 if ((typeBits & (1L << i.ordinal())) != 0L) {
297 storedTypes.add(i);
298 }
299 }
300 }
301
302 public void writeToParcel(Parcel out, int flags) {
303 out.writeString(alias);
304
305 long typeBits = 0;
306 for (Type i : storedTypes) {
307 typeBits |= 1L << i.ordinal();
308 }
309 out.writeLong(typeBits);
310 }
311
312 public int describeContents() {
313 return 0;
314 }
315
316 public static final Parcelable.Creator<Credential> CREATOR
317 = new Parcelable.Creator<Credential>() {
318 public Credential createFromParcel(Parcel in) {
319 return new Credential(in);
320 }
321
322 public Credential[] newArray(int size) {
323 return new Credential[size];
324 }
325 };
Robin Leebaefdcf2015-08-26 10:57:44 +0100326 }
327}