blob: 96ef8e9c4a4d806a48452ec01a009e88f5dda4dd [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 Leebaefdcf2015-08-26 10:57:44 +010030import android.security.Credentials;
Robin Leeda7bc512016-02-24 17:39:32 +000031import android.security.IKeyChainService;
32import android.security.KeyChain;
33import android.security.KeyChain.KeyChainConnection;
Robin Leebaefdcf2015-08-26 10:57:44 +010034import android.security.KeyStore;
Robin Leeda7bc512016-02-24 17:39:32 +000035import android.util.Log;
Robin Leebaefdcf2015-08-26 10:57:44 +010036import android.view.LayoutInflater;
37import android.view.View;
38import android.view.ViewGroup;
Robin Leebaefdcf2015-08-26 10:57:44 +010039import android.widget.AdapterView;
40import android.widget.AdapterView.OnItemClickListener;
41import android.widget.ArrayAdapter;
Robin Leebaefdcf2015-08-26 10:57:44 +010042import android.widget.ListView;
43import android.widget.TextView;
44
Robin Leeb70b3d82016-02-01 12:52:16 +000045import com.android.internal.logging.MetricsProto.MetricsEvent;
46
Robin Leebaefdcf2015-08-26 10:57:44 +010047import java.util.EnumSet;
Robin Leebaefdcf2015-08-26 10:57:44 +010048import java.util.SortedMap;
49import java.util.TreeMap;
50
Robin Leebaefdcf2015-08-26 10:57:44 +010051import static android.view.View.GONE;
Jason Monk39b46742015-09-10 15:52:51 -040052import static android.view.View.VISIBLE;
Robin Leebaefdcf2015-08-26 10:57:44 +010053
Udam Saini0708d9e2016-03-28 16:35:13 -070054public class UserCredentialsSettings extends OptionsMenuFragment implements OnItemClickListener {
Robin Leebaefdcf2015-08-26 10:57:44 +010055 private static final String TAG = "UserCredentialsSettings";
56
57 private View mRootView;
58 private ListView mListView;
59
60 @Override
61 protected int getMetricsCategory() {
Robin Leeb70b3d82016-02-01 12:52:16 +000062 return MetricsEvent.USER_CREDENTIALS;
Robin Leebaefdcf2015-08-26 10:57:44 +010063 }
64
65 @Override
66 public void onResume() {
67 super.onResume();
Robin Lee04046a12016-01-19 11:42:57 +000068 refreshItems();
Robin Leebaefdcf2015-08-26 10:57:44 +010069 }
70
71 @Override
72 public View onCreateView(
73 LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
74 mRootView = inflater.inflate(R.layout.user_credentials, parent, false);
75
76 // Set up an OnItemClickListener for the credential list.
77 mListView = (ListView) mRootView.findViewById(R.id.credential_list);
78 mListView.setOnItemClickListener(this);
79
80 return mRootView;
81 }
82
83 @Override
84 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
85 final Credential item = (Credential) parent.getItemAtPosition(position);
Robin Lee04046a12016-01-19 11:42:57 +000086 CredentialDialogFragment.show(this, item);
87 }
Robin Leebaefdcf2015-08-26 10:57:44 +010088
Robin Lee04046a12016-01-19 11:42:57 +000089 protected void refreshItems() {
90 if (isAdded()) {
91 new AliasLoader().execute();
92 }
93 }
Robin Leebaefdcf2015-08-26 10:57:44 +010094
Robin Lee04046a12016-01-19 11:42:57 +000095 public static class CredentialDialogFragment extends DialogFragment {
96 private static final String TAG = "CredentialDialogFragment";
97 private static final String ARG_CREDENTIAL = "credential";
98
99 public static void show(Fragment target, Credential item) {
100 final Bundle args = new Bundle();
101 args.putParcelable(ARG_CREDENTIAL, item);
102
103 final CredentialDialogFragment frag = new CredentialDialogFragment();
104 frag.setTargetFragment(target, /* requestCode */ -1);
105 frag.setArguments(args);
106 frag.show(target.getFragmentManager(), TAG);
107 }
108
109 @Override
110 public Dialog onCreateDialog(Bundle savedInstanceState) {
111 final Credential item = (Credential) getArguments().getParcelable(ARG_CREDENTIAL);
112 View root = getActivity().getLayoutInflater()
113 .inflate(R.layout.user_credential_dialog, null);
114 ViewGroup infoContainer = (ViewGroup) root.findViewById(R.id.credential_container);
115 View view = new CredentialAdapter(getActivity(), R.layout.user_credential,
116 new Credential[] {item}).getView(0, null, null);
117 infoContainer.addView(view);
118
119 return new AlertDialog.Builder(getActivity())
120 .setView(root)
121 .setTitle(R.string.user_credential_title)
122 .setPositiveButton(R.string.done, null)
123 .setNegativeButton(R.string.trusted_credentials_remove_label,
124 new DialogInterface.OnClickListener() {
125 @Override public void onClick(DialogInterface dialog, int id) {
Robin Leeda7bc512016-02-24 17:39:32 +0000126 new RemoveCredentialsTask(getContext(), getTargetFragment())
127 .execute(item.alias);
Robin Lee04046a12016-01-19 11:42:57 +0000128 dialog.dismiss();
129 }
130 })
131 .create();
132 }
133
Robin Leeda7bc512016-02-24 17:39:32 +0000134 private class RemoveCredentialsTask extends AsyncTask<String, Void, Void> {
135 private Context context;
136 private Fragment targetFragment;
137
138 public RemoveCredentialsTask(Context context, Fragment targetFragment) {
139 this.context = context;
140 this.targetFragment = targetFragment;
Robin Lee04046a12016-01-19 11:42:57 +0000141 }
Robin Leeda7bc512016-02-24 17:39:32 +0000142
143 @Override
144 protected Void doInBackground(String... aliases) {
145 try {
146 final KeyChainConnection conn = KeyChain.bind(getContext());
147 try {
148 IKeyChainService keyChain = conn.getService();
149 for (String alias : aliases) {
150 keyChain.removeKeyPair(alias);
151 }
152 } catch (RemoteException e) {
153 Log.w(TAG, "Removing credentials", e);
154 } finally {
155 conn.close();
156 }
157 } catch (InterruptedException e) {
158 Log.w(TAG, "Connecting to keychain", e);
159 }
160 return null;
161 }
162
163 @Override
164 protected void onPostExecute(Void result) {
165 if (targetFragment instanceof UserCredentialsSettings) {
166 ((UserCredentialsSettings) targetFragment).refreshItems();
167 }
168 }
Robin Lee04046a12016-01-19 11:42:57 +0000169 }
Robin Leebaefdcf2015-08-26 10:57:44 +0100170 }
171
172 /**
173 * Opens a background connection to KeyStore to list user credentials.
174 * The credentials are stored in a {@link CredentialAdapter} attached to the main
175 * {@link ListView} in the fragment.
176 */
Robin Lee04046a12016-01-19 11:42:57 +0000177 private class AliasLoader extends AsyncTask<Void, Void, SortedMap<String, Credential>> {
Robin Leebaefdcf2015-08-26 10:57:44 +0100178 @Override
Robin Lee04046a12016-01-19 11:42:57 +0000179 protected SortedMap<String, Credential> doInBackground(Void... params) {
Robin Leebaefdcf2015-08-26 10:57:44 +0100180 // Create a list of names for credential sets, ordered by name.
181 SortedMap<String, Credential> credentials = new TreeMap<>();
182 KeyStore keyStore = KeyStore.getInstance();
183 for (final Credential.Type type : Credential.Type.values()) {
184 for (final String alias : keyStore.list(type.prefix)) {
185 Credential c = credentials.get(alias);
186 if (c == null) {
187 credentials.put(alias, (c = new Credential(alias)));
188 }
189 c.storedTypes.add(type);
190 }
191 }
Robin Lee04046a12016-01-19 11:42:57 +0000192 return credentials;
Robin Leebaefdcf2015-08-26 10:57:44 +0100193 }
194
195 @Override
Robin Lee04046a12016-01-19 11:42:57 +0000196 protected void onPostExecute(SortedMap<String, Credential> credentials) {
197 // Convert the results to an array and present them using an ArrayAdapter.
198 mListView.setAdapter(new CredentialAdapter(getContext(), R.layout.user_credential,
199 credentials.values().toArray(new Credential[0])));
Robin Leebaefdcf2015-08-26 10:57:44 +0100200 }
201 }
202
203 /**
204 * Helper class to display {@link Credential}s in a list.
205 */
206 private static class CredentialAdapter extends ArrayAdapter<Credential> {
207 public CredentialAdapter(Context context, int resource, Credential[] objects) {
208 super(context, resource, objects);
209 }
210
211 @Override
212 public View getView(int position, View view, ViewGroup parent) {
213 if (view == null) {
214 view = LayoutInflater.from(getContext())
215 .inflate(R.layout.user_credential, parent, false);
216 }
217 Credential item = getItem(position);
218 ((TextView) view.findViewById(R.id.alias)).setText(item.alias);
219 view.findViewById(R.id.contents_userkey).setVisibility(
220 item.storedTypes.contains(Credential.Type.USER_PRIVATE_KEY) ? VISIBLE : GONE);
221 view.findViewById(R.id.contents_usercrt).setVisibility(
222 item.storedTypes.contains(Credential.Type.USER_CERTIFICATE) ? VISIBLE : GONE);
223 view.findViewById(R.id.contents_cacrt).setVisibility(
224 item.storedTypes.contains(Credential.Type.CA_CERTIFICATE) ? VISIBLE : GONE);
225 return view;
226 }
227 }
228
Robin Leee2680422016-01-25 12:24:27 +0000229 static class Credential implements Parcelable {
230 static enum Type {
Robin Leebaefdcf2015-08-26 10:57:44 +0100231 CA_CERTIFICATE (Credentials.CA_CERTIFICATE),
232 USER_CERTIFICATE (Credentials.USER_CERTIFICATE),
233 USER_PRIVATE_KEY (Credentials.USER_PRIVATE_KEY),
234 USER_SECRET_KEY (Credentials.USER_SECRET_KEY);
235
236 final String prefix;
237
238 Type(String prefix) {
239 this.prefix = prefix;
240 }
241 }
242
243 /**
244 * Main part of the credential's alias. To fetch an item from KeyStore, prepend one of the
245 * prefixes from {@link CredentialItem.storedTypes}.
246 */
247 final String alias;
248
249 /**
250 * Should contain some non-empty subset of:
251 * <ul>
252 * <li>{@link Credentials.CA_CERTIFICATE}</li>
253 * <li>{@link Credentials.USER_CERTIFICATE}</li>
254 * <li>{@link Credentials.USER_PRIVATE_KEY}</li>
255 * <li>{@link Credentials.USER_SECRET_KEY}</li>
256 * </ul>
257 */
Robin Lee04046a12016-01-19 11:42:57 +0000258 final EnumSet<Type> storedTypes = EnumSet.noneOf(Type.class);
Robin Leebaefdcf2015-08-26 10:57:44 +0100259
260 Credential(final String alias) {
261 this.alias = alias;
262 }
Robin Lee04046a12016-01-19 11:42:57 +0000263
264 Credential(Parcel in) {
265 this(in.readString());
266
267 long typeBits = in.readLong();
268 for (Type i : Type.values()) {
269 if ((typeBits & (1L << i.ordinal())) != 0L) {
270 storedTypes.add(i);
271 }
272 }
273 }
274
275 public void writeToParcel(Parcel out, int flags) {
276 out.writeString(alias);
277
278 long typeBits = 0;
279 for (Type i : storedTypes) {
280 typeBits |= 1L << i.ordinal();
281 }
282 out.writeLong(typeBits);
283 }
284
285 public int describeContents() {
286 return 0;
287 }
288
289 public static final Parcelable.Creator<Credential> CREATOR
290 = new Parcelable.Creator<Credential>() {
291 public Credential createFromParcel(Parcel in) {
292 return new Credential(in);
293 }
294
295 public Credential[] newArray(int size) {
296 return new Credential[size];
297 }
298 };
Robin Leebaefdcf2015-08-26 10:57:44 +0100299 }
300}