blob: e68845016464a4b0648dbc2dcf3eecc4dafe5419 [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;
23import android.app.FragmentManager;
Robin Leebaefdcf2015-08-26 10:57:44 +010024import android.content.Context;
25import android.content.DialogInterface;
26import android.os.AsyncTask;
27import android.os.Bundle;
Robin Lee04046a12016-01-19 11:42:57 +000028import android.os.Parcel;
29import android.os.Parcelable;
Robin Leeda7bc512016-02-24 17:39:32 +000030import android.os.RemoteException;
Robin Leebaefdcf2015-08-26 10:57:44 +010031import android.security.Credentials;
Robin Leeda7bc512016-02-24 17:39:32 +000032import android.security.IKeyChainService;
33import android.security.KeyChain;
34import android.security.KeyChain.KeyChainConnection;
Robin Leebaefdcf2015-08-26 10:57:44 +010035import android.security.KeyStore;
Robin Leeda7bc512016-02-24 17:39:32 +000036import android.util.Log;
Robin Leebaefdcf2015-08-26 10:57:44 +010037import android.view.LayoutInflater;
38import android.view.View;
39import android.view.ViewGroup;
Robin Leebaefdcf2015-08-26 10:57:44 +010040import android.widget.AdapterView;
41import android.widget.AdapterView.OnItemClickListener;
42import android.widget.ArrayAdapter;
Robin Leebaefdcf2015-08-26 10:57:44 +010043import android.widget.ListView;
44import android.widget.TextView;
45
Robin Leeb70b3d82016-02-01 12:52:16 +000046import com.android.internal.logging.MetricsProto.MetricsEvent;
47
Robin Leebaefdcf2015-08-26 10:57:44 +010048import java.util.EnumSet;
Robin Leebaefdcf2015-08-26 10:57:44 +010049import java.util.Set;
50import java.util.SortedMap;
51import java.util.TreeMap;
52
Robin Leebaefdcf2015-08-26 10:57:44 +010053import static android.view.View.GONE;
Jason Monk39b46742015-09-10 15:52:51 -040054import static android.view.View.VISIBLE;
Robin Leebaefdcf2015-08-26 10:57:44 +010055
56public class UserCredentialsSettings extends InstrumentedFragment implements OnItemClickListener {
57 private static final String TAG = "UserCredentialsSettings";
58
59 private View mRootView;
60 private ListView mListView;
61
62 @Override
63 protected int getMetricsCategory() {
Robin Leeb70b3d82016-02-01 12:52:16 +000064 return MetricsEvent.USER_CREDENTIALS;
Robin Leebaefdcf2015-08-26 10:57:44 +010065 }
66
67 @Override
68 public void onResume() {
69 super.onResume();
Robin Lee04046a12016-01-19 11:42:57 +000070 refreshItems();
Robin Leebaefdcf2015-08-26 10:57:44 +010071 }
72
73 @Override
74 public View onCreateView(
75 LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
76 mRootView = inflater.inflate(R.layout.user_credentials, parent, false);
77
78 // Set up an OnItemClickListener for the credential list.
79 mListView = (ListView) mRootView.findViewById(R.id.credential_list);
80 mListView.setOnItemClickListener(this);
81
82 return mRootView;
83 }
84
85 @Override
86 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
87 final Credential item = (Credential) parent.getItemAtPosition(position);
Robin Lee04046a12016-01-19 11:42:57 +000088 CredentialDialogFragment.show(this, item);
89 }
Robin Leebaefdcf2015-08-26 10:57:44 +010090
Robin Lee04046a12016-01-19 11:42:57 +000091 protected void refreshItems() {
92 if (isAdded()) {
93 new AliasLoader().execute();
94 }
95 }
Robin Leebaefdcf2015-08-26 10:57:44 +010096
Robin Lee04046a12016-01-19 11:42:57 +000097 public static class CredentialDialogFragment extends DialogFragment {
98 private static final String TAG = "CredentialDialogFragment";
99 private static final String ARG_CREDENTIAL = "credential";
100
101 public static void show(Fragment target, Credential item) {
102 final Bundle args = new Bundle();
103 args.putParcelable(ARG_CREDENTIAL, item);
104
105 final CredentialDialogFragment frag = new CredentialDialogFragment();
106 frag.setTargetFragment(target, /* requestCode */ -1);
107 frag.setArguments(args);
108 frag.show(target.getFragmentManager(), TAG);
109 }
110
111 @Override
112 public Dialog onCreateDialog(Bundle savedInstanceState) {
113 final Credential item = (Credential) getArguments().getParcelable(ARG_CREDENTIAL);
114 View root = getActivity().getLayoutInflater()
115 .inflate(R.layout.user_credential_dialog, null);
116 ViewGroup infoContainer = (ViewGroup) root.findViewById(R.id.credential_container);
117 View view = new CredentialAdapter(getActivity(), R.layout.user_credential,
118 new Credential[] {item}).getView(0, null, null);
119 infoContainer.addView(view);
120
121 return new AlertDialog.Builder(getActivity())
122 .setView(root)
123 .setTitle(R.string.user_credential_title)
124 .setPositiveButton(R.string.done, null)
125 .setNegativeButton(R.string.trusted_credentials_remove_label,
126 new DialogInterface.OnClickListener() {
127 @Override public void onClick(DialogInterface dialog, int id) {
Robin Leeda7bc512016-02-24 17:39:32 +0000128 new RemoveCredentialsTask(getContext(), getTargetFragment())
129 .execute(item.alias);
Robin Lee04046a12016-01-19 11:42:57 +0000130 dialog.dismiss();
131 }
132 })
133 .create();
134 }
135
Robin Leeda7bc512016-02-24 17:39:32 +0000136 private class RemoveCredentialsTask extends AsyncTask<String, Void, Void> {
137 private Context context;
138 private Fragment targetFragment;
139
140 public RemoveCredentialsTask(Context context, Fragment targetFragment) {
141 this.context = context;
142 this.targetFragment = targetFragment;
Robin Lee04046a12016-01-19 11:42:57 +0000143 }
Robin Leeda7bc512016-02-24 17:39:32 +0000144
145 @Override
146 protected Void doInBackground(String... aliases) {
147 try {
148 final KeyChainConnection conn = KeyChain.bind(getContext());
149 try {
150 IKeyChainService keyChain = conn.getService();
151 for (String alias : aliases) {
152 keyChain.removeKeyPair(alias);
153 }
154 } catch (RemoteException e) {
155 Log.w(TAG, "Removing credentials", e);
156 } finally {
157 conn.close();
158 }
159 } catch (InterruptedException e) {
160 Log.w(TAG, "Connecting to keychain", e);
161 }
162 return null;
163 }
164
165 @Override
166 protected void onPostExecute(Void result) {
167 if (targetFragment instanceof UserCredentialsSettings) {
168 ((UserCredentialsSettings) targetFragment).refreshItems();
169 }
170 }
Robin Lee04046a12016-01-19 11:42:57 +0000171 }
Robin Leebaefdcf2015-08-26 10:57:44 +0100172 }
173
174 /**
175 * Opens a background connection to KeyStore to list user credentials.
176 * The credentials are stored in a {@link CredentialAdapter} attached to the main
177 * {@link ListView} in the fragment.
178 */
Robin Lee04046a12016-01-19 11:42:57 +0000179 private class AliasLoader extends AsyncTask<Void, Void, SortedMap<String, Credential>> {
Robin Leebaefdcf2015-08-26 10:57:44 +0100180 @Override
Robin Lee04046a12016-01-19 11:42:57 +0000181 protected SortedMap<String, Credential> doInBackground(Void... params) {
Robin Leebaefdcf2015-08-26 10:57:44 +0100182 // Create a list of names for credential sets, ordered by name.
183 SortedMap<String, Credential> credentials = new TreeMap<>();
184 KeyStore keyStore = KeyStore.getInstance();
185 for (final Credential.Type type : Credential.Type.values()) {
186 for (final String alias : keyStore.list(type.prefix)) {
187 Credential c = credentials.get(alias);
188 if (c == null) {
189 credentials.put(alias, (c = new Credential(alias)));
190 }
191 c.storedTypes.add(type);
192 }
193 }
Robin Lee04046a12016-01-19 11:42:57 +0000194 return credentials;
Robin Leebaefdcf2015-08-26 10:57:44 +0100195 }
196
197 @Override
Robin Lee04046a12016-01-19 11:42:57 +0000198 protected void onPostExecute(SortedMap<String, Credential> credentials) {
199 // Convert the results to an array and present them using an ArrayAdapter.
200 mListView.setAdapter(new CredentialAdapter(getContext(), R.layout.user_credential,
201 credentials.values().toArray(new Credential[0])));
Robin Leebaefdcf2015-08-26 10:57:44 +0100202 }
203 }
204
205 /**
206 * Helper class to display {@link Credential}s in a list.
207 */
208 private static class CredentialAdapter extends ArrayAdapter<Credential> {
209 public CredentialAdapter(Context context, int resource, Credential[] objects) {
210 super(context, resource, objects);
211 }
212
213 @Override
214 public View getView(int position, View view, ViewGroup parent) {
215 if (view == null) {
216 view = LayoutInflater.from(getContext())
217 .inflate(R.layout.user_credential, parent, false);
218 }
219 Credential item = getItem(position);
220 ((TextView) view.findViewById(R.id.alias)).setText(item.alias);
221 view.findViewById(R.id.contents_userkey).setVisibility(
222 item.storedTypes.contains(Credential.Type.USER_PRIVATE_KEY) ? VISIBLE : GONE);
223 view.findViewById(R.id.contents_usercrt).setVisibility(
224 item.storedTypes.contains(Credential.Type.USER_CERTIFICATE) ? VISIBLE : GONE);
225 view.findViewById(R.id.contents_cacrt).setVisibility(
226 item.storedTypes.contains(Credential.Type.CA_CERTIFICATE) ? VISIBLE : GONE);
227 return view;
228 }
229 }
230
Robin Leee2680422016-01-25 12:24:27 +0000231 static class Credential implements Parcelable {
232 static enum Type {
Robin Leebaefdcf2015-08-26 10:57:44 +0100233 CA_CERTIFICATE (Credentials.CA_CERTIFICATE),
234 USER_CERTIFICATE (Credentials.USER_CERTIFICATE),
235 USER_PRIVATE_KEY (Credentials.USER_PRIVATE_KEY),
236 USER_SECRET_KEY (Credentials.USER_SECRET_KEY);
237
238 final String prefix;
239
240 Type(String prefix) {
241 this.prefix = prefix;
242 }
243 }
244
245 /**
246 * Main part of the credential's alias. To fetch an item from KeyStore, prepend one of the
247 * prefixes from {@link CredentialItem.storedTypes}.
248 */
249 final String alias;
250
251 /**
252 * Should contain some non-empty subset of:
253 * <ul>
254 * <li>{@link Credentials.CA_CERTIFICATE}</li>
255 * <li>{@link Credentials.USER_CERTIFICATE}</li>
256 * <li>{@link Credentials.USER_PRIVATE_KEY}</li>
257 * <li>{@link Credentials.USER_SECRET_KEY}</li>
258 * </ul>
259 */
Robin Lee04046a12016-01-19 11:42:57 +0000260 final EnumSet<Type> storedTypes = EnumSet.noneOf(Type.class);
Robin Leebaefdcf2015-08-26 10:57:44 +0100261
262 Credential(final String alias) {
263 this.alias = alias;
264 }
Robin Lee04046a12016-01-19 11:42:57 +0000265
266 Credential(Parcel in) {
267 this(in.readString());
268
269 long typeBits = in.readLong();
270 for (Type i : Type.values()) {
271 if ((typeBits & (1L << i.ordinal())) != 0L) {
272 storedTypes.add(i);
273 }
274 }
275 }
276
277 public void writeToParcel(Parcel out, int flags) {
278 out.writeString(alias);
279
280 long typeBits = 0;
281 for (Type i : storedTypes) {
282 typeBits |= 1L << i.ordinal();
283 }
284 out.writeLong(typeBits);
285 }
286
287 public int describeContents() {
288 return 0;
289 }
290
291 public static final Parcelable.Creator<Credential> CREATOR
292 = new Parcelable.Creator<Credential>() {
293 public Credential createFromParcel(Parcel in) {
294 return new Credential(in);
295 }
296
297 public Credential[] newArray(int size) {
298 return new Credential[size];
299 }
300 };
Robin Leebaefdcf2015-08-26 10:57:44 +0100301 }
302}