blob: 535c301b63c6d0406581defaf444e6a1e606928a [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;
Robin Leec421db72016-03-11 16:22:23 +000048import com.android.settingslib.RestrictedLockUtils;
49import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
Robin Leeb70b3d82016-02-01 12:52:16 +000050
Robin Leebaefdcf2015-08-26 10:57:44 +010051import java.util.EnumSet;
Robin Leebaefdcf2015-08-26 10:57:44 +010052import java.util.SortedMap;
53import java.util.TreeMap;
54
Robin Leebaefdcf2015-08-26 10:57:44 +010055import static android.view.View.GONE;
Jason Monk39b46742015-09-10 15:52:51 -040056import static android.view.View.VISIBLE;
Robin Leebaefdcf2015-08-26 10:57:44 +010057
Udam Saini0708d9e2016-03-28 16:35:13 -070058public class UserCredentialsSettings extends OptionsMenuFragment implements OnItemClickListener {
Robin Leebaefdcf2015-08-26 10:57:44 +010059 private static final String TAG = "UserCredentialsSettings";
60
61 private View mRootView;
62 private ListView mListView;
63
64 @Override
65 protected int getMetricsCategory() {
Robin Leeb70b3d82016-02-01 12:52:16 +000066 return MetricsEvent.USER_CREDENTIALS;
Robin Leebaefdcf2015-08-26 10:57:44 +010067 }
68
69 @Override
70 public void onResume() {
71 super.onResume();
Robin Lee04046a12016-01-19 11:42:57 +000072 refreshItems();
Robin Leebaefdcf2015-08-26 10:57:44 +010073 }
74
75 @Override
76 public View onCreateView(
77 LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
78 mRootView = inflater.inflate(R.layout.user_credentials, parent, false);
79
80 // Set up an OnItemClickListener for the credential list.
81 mListView = (ListView) mRootView.findViewById(R.id.credential_list);
82 mListView.setOnItemClickListener(this);
83
84 return mRootView;
85 }
86
87 @Override
88 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
89 final Credential item = (Credential) parent.getItemAtPosition(position);
Robin Lee04046a12016-01-19 11:42:57 +000090 CredentialDialogFragment.show(this, item);
91 }
Robin Leebaefdcf2015-08-26 10:57:44 +010092
Robin Lee11fd5502016-05-16 15:42:34 +010093 protected void announceRemoval(String alias) {
94 if (isAdded()) {
95 mListView.announceForAccessibility(getString(R.string.user_credential_removed, alias));
96 }
97 }
98
Robin Lee04046a12016-01-19 11:42:57 +000099 protected void refreshItems() {
100 if (isAdded()) {
101 new AliasLoader().execute();
102 }
103 }
Robin Leebaefdcf2015-08-26 10:57:44 +0100104
Robin Lee04046a12016-01-19 11:42:57 +0000105 public static class CredentialDialogFragment extends DialogFragment {
106 private static final String TAG = "CredentialDialogFragment";
107 private static final String ARG_CREDENTIAL = "credential";
108
109 public static void show(Fragment target, Credential item) {
110 final Bundle args = new Bundle();
111 args.putParcelable(ARG_CREDENTIAL, item);
112
Robin Leef8e2dbf2016-04-07 13:17:24 +0100113 if (target.getFragmentManager().findFragmentByTag(TAG) == null) {
114 final DialogFragment frag = new CredentialDialogFragment();
115 frag.setTargetFragment(target, /* requestCode */ -1);
116 frag.setArguments(args);
117 frag.show(target.getFragmentManager(), TAG);
118 }
Robin Lee04046a12016-01-19 11:42:57 +0000119 }
120
121 @Override
122 public Dialog onCreateDialog(Bundle savedInstanceState) {
123 final Credential item = (Credential) getArguments().getParcelable(ARG_CREDENTIAL);
124 View root = getActivity().getLayoutInflater()
125 .inflate(R.layout.user_credential_dialog, null);
126 ViewGroup infoContainer = (ViewGroup) root.findViewById(R.id.credential_container);
127 View view = new CredentialAdapter(getActivity(), R.layout.user_credential,
128 new Credential[] {item}).getView(0, null, null);
129 infoContainer.addView(view);
130
Robin Leec421db72016-03-11 16:22:23 +0000131 UserManager userManager
132 = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
133
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 Lee11fd5502016-05-16 15:42:34 +0100150 new RemoveCredentialsTask(getTargetFragment()).execute(item.alias);
Robin Leec421db72016-03-11 16:22:23 +0000151 }
152 dialog.dismiss();
153 }
154 };
155 builder.setNegativeButton(R.string.trusted_credentials_remove_label, listener);
156 }
157 return builder.create();
Robin Lee04046a12016-01-19 11:42:57 +0000158 }
159
Robin Lee11fd5502016-05-16 15:42:34 +0100160 private class RemoveCredentialsTask extends AsyncTask<String, Void, String[]> {
Robin Leeda7bc512016-02-24 17:39:32 +0000161 private Fragment targetFragment;
162
Robin Lee11fd5502016-05-16 15:42:34 +0100163 public RemoveCredentialsTask(Fragment targetFragment) {
Robin Leeda7bc512016-02-24 17:39:32 +0000164 this.targetFragment = targetFragment;
Robin Lee04046a12016-01-19 11:42:57 +0000165 }
Robin Leeda7bc512016-02-24 17:39:32 +0000166
167 @Override
Robin Lee11fd5502016-05-16 15:42:34 +0100168 protected String[] doInBackground(String... aliases) {
Robin Leeda7bc512016-02-24 17:39:32 +0000169 try {
170 final KeyChainConnection conn = KeyChain.bind(getContext());
171 try {
172 IKeyChainService keyChain = conn.getService();
173 for (String alias : aliases) {
174 keyChain.removeKeyPair(alias);
175 }
176 } catch (RemoteException e) {
177 Log.w(TAG, "Removing credentials", e);
178 } finally {
179 conn.close();
180 }
181 } catch (InterruptedException e) {
182 Log.w(TAG, "Connecting to keychain", e);
183 }
Robin Lee11fd5502016-05-16 15:42:34 +0100184 return aliases;
Robin Leeda7bc512016-02-24 17:39:32 +0000185 }
186
187 @Override
Robin Lee11fd5502016-05-16 15:42:34 +0100188 protected void onPostExecute(String... aliases) {
189 if (targetFragment instanceof UserCredentialsSettings && targetFragment.isAdded()) {
190 final UserCredentialsSettings target = (UserCredentialsSettings) targetFragment;
191 for (final String alias : aliases) {
192 target.announceRemoval(alias);
193 }
194 target.refreshItems();
Robin Leeda7bc512016-02-24 17:39:32 +0000195 }
196 }
Robin Lee04046a12016-01-19 11:42:57 +0000197 }
Robin Leebaefdcf2015-08-26 10:57:44 +0100198 }
199
200 /**
201 * Opens a background connection to KeyStore to list user credentials.
202 * The credentials are stored in a {@link CredentialAdapter} attached to the main
203 * {@link ListView} in the fragment.
204 */
Robin Lee04046a12016-01-19 11:42:57 +0000205 private class AliasLoader extends AsyncTask<Void, Void, SortedMap<String, Credential>> {
Robin Leebaefdcf2015-08-26 10:57:44 +0100206 @Override
Robin Lee04046a12016-01-19 11:42:57 +0000207 protected SortedMap<String, Credential> doInBackground(Void... params) {
Robin Leebaefdcf2015-08-26 10:57:44 +0100208 // Create a list of names for credential sets, ordered by name.
209 SortedMap<String, Credential> credentials = new TreeMap<>();
210 KeyStore keyStore = KeyStore.getInstance();
211 for (final Credential.Type type : Credential.Type.values()) {
212 for (final String alias : keyStore.list(type.prefix)) {
213 Credential c = credentials.get(alias);
214 if (c == null) {
215 credentials.put(alias, (c = new Credential(alias)));
216 }
217 c.storedTypes.add(type);
218 }
219 }
Robin Lee04046a12016-01-19 11:42:57 +0000220 return credentials;
Robin Leebaefdcf2015-08-26 10:57:44 +0100221 }
222
223 @Override
Robin Lee04046a12016-01-19 11:42:57 +0000224 protected void onPostExecute(SortedMap<String, Credential> credentials) {
225 // Convert the results to an array and present them using an ArrayAdapter.
226 mListView.setAdapter(new CredentialAdapter(getContext(), R.layout.user_credential,
227 credentials.values().toArray(new Credential[0])));
Robin Leebaefdcf2015-08-26 10:57:44 +0100228 }
229 }
230
231 /**
232 * Helper class to display {@link Credential}s in a list.
233 */
234 private static class CredentialAdapter extends ArrayAdapter<Credential> {
235 public CredentialAdapter(Context context, int resource, Credential[] objects) {
236 super(context, resource, objects);
237 }
238
239 @Override
240 public View getView(int position, View view, ViewGroup parent) {
241 if (view == null) {
242 view = LayoutInflater.from(getContext())
243 .inflate(R.layout.user_credential, parent, false);
244 }
245 Credential item = getItem(position);
246 ((TextView) view.findViewById(R.id.alias)).setText(item.alias);
247 view.findViewById(R.id.contents_userkey).setVisibility(
248 item.storedTypes.contains(Credential.Type.USER_PRIVATE_KEY) ? VISIBLE : GONE);
249 view.findViewById(R.id.contents_usercrt).setVisibility(
250 item.storedTypes.contains(Credential.Type.USER_CERTIFICATE) ? VISIBLE : GONE);
251 view.findViewById(R.id.contents_cacrt).setVisibility(
252 item.storedTypes.contains(Credential.Type.CA_CERTIFICATE) ? VISIBLE : GONE);
253 return view;
254 }
255 }
256
Robin Leee2680422016-01-25 12:24:27 +0000257 static class Credential implements Parcelable {
258 static enum Type {
Robin Leebaefdcf2015-08-26 10:57:44 +0100259 CA_CERTIFICATE (Credentials.CA_CERTIFICATE),
260 USER_CERTIFICATE (Credentials.USER_CERTIFICATE),
261 USER_PRIVATE_KEY (Credentials.USER_PRIVATE_KEY),
262 USER_SECRET_KEY (Credentials.USER_SECRET_KEY);
263
264 final String prefix;
265
266 Type(String prefix) {
267 this.prefix = prefix;
268 }
269 }
270
271 /**
272 * Main part of the credential's alias. To fetch an item from KeyStore, prepend one of the
273 * prefixes from {@link CredentialItem.storedTypes}.
274 */
275 final String alias;
276
277 /**
278 * Should contain some non-empty subset of:
279 * <ul>
280 * <li>{@link Credentials.CA_CERTIFICATE}</li>
281 * <li>{@link Credentials.USER_CERTIFICATE}</li>
282 * <li>{@link Credentials.USER_PRIVATE_KEY}</li>
283 * <li>{@link Credentials.USER_SECRET_KEY}</li>
284 * </ul>
285 */
Robin Lee04046a12016-01-19 11:42:57 +0000286 final EnumSet<Type> storedTypes = EnumSet.noneOf(Type.class);
Robin Leebaefdcf2015-08-26 10:57:44 +0100287
288 Credential(final String alias) {
289 this.alias = alias;
290 }
Robin Lee04046a12016-01-19 11:42:57 +0000291
292 Credential(Parcel in) {
293 this(in.readString());
294
295 long typeBits = in.readLong();
296 for (Type i : Type.values()) {
297 if ((typeBits & (1L << i.ordinal())) != 0L) {
298 storedTypes.add(i);
299 }
300 }
301 }
302
303 public void writeToParcel(Parcel out, int flags) {
304 out.writeString(alias);
305
306 long typeBits = 0;
307 for (Type i : storedTypes) {
308 typeBits |= 1L << i.ordinal();
309 }
310 out.writeLong(typeBits);
311 }
312
313 public int describeContents() {
314 return 0;
315 }
316
317 public static final Parcelable.Creator<Credential> CREATOR
318 = new Parcelable.Creator<Credential>() {
319 public Credential createFromParcel(Parcel in) {
320 return new Credential(in);
321 }
322
323 public Credential[] newArray(int size) {
324 return new Credential[size];
325 }
326 };
Robin Leebaefdcf2015-08-26 10:57:44 +0100327 }
328}