UserCredentialsSettings.java revision 0708d9e119da4c4d9424c0bc54fa458d01856bd7
1/*
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;
20import android.app.Dialog;
21import android.app.DialogFragment;
22import android.app.Fragment;
23import android.content.Context;
24import android.content.DialogInterface;
25import android.os.AsyncTask;
26import android.os.Bundle;
27import android.os.Parcel;
28import android.os.Parcelable;
29import android.os.RemoteException;
30import android.security.Credentials;
31import android.security.IKeyChainService;
32import android.security.KeyChain;
33import android.security.KeyChain.KeyChainConnection;
34import android.security.KeyStore;
35import android.util.Log;
36import android.view.LayoutInflater;
37import android.view.View;
38import android.view.ViewGroup;
39import android.widget.AdapterView;
40import android.widget.AdapterView.OnItemClickListener;
41import android.widget.ArrayAdapter;
42import android.widget.ListView;
43import android.widget.TextView;
44
45import com.android.internal.logging.MetricsProto.MetricsEvent;
46
47import java.util.EnumSet;
48import java.util.SortedMap;
49import java.util.TreeMap;
50
51import static android.view.View.GONE;
52import static android.view.View.VISIBLE;
53
54public class UserCredentialsSettings extends OptionsMenuFragment implements OnItemClickListener {
55    private static final String TAG = "UserCredentialsSettings";
56
57    private View mRootView;
58    private ListView mListView;
59
60    @Override
61    protected int getMetricsCategory() {
62        return MetricsEvent.USER_CREDENTIALS;
63    }
64
65    @Override
66    public void onResume() {
67        super.onResume();
68        refreshItems();
69    }
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);
86        CredentialDialogFragment.show(this, item);
87    }
88
89    protected void refreshItems() {
90        if (isAdded()) {
91            new AliasLoader().execute();
92        }
93    }
94
95    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) {
126                                    new RemoveCredentialsTask(getContext(), getTargetFragment())
127                                            .execute(item.alias);
128                                    dialog.dismiss();
129                                }
130                            })
131                    .create();
132        }
133
134        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;
141            }
142
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            }
169        }
170    }
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     */
177    private class AliasLoader extends AsyncTask<Void, Void, SortedMap<String, Credential>> {
178        @Override
179        protected SortedMap<String, Credential> doInBackground(Void... params) {
180            // 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            }
192            return credentials;
193        }
194
195        @Override
196        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])));
200        }
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
229    static class Credential implements Parcelable {
230        static enum Type {
231            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         */
258        final EnumSet<Type> storedTypes = EnumSet.noneOf(Type.class);
259
260        Credential(final String alias) {
261            this.alias = alias;
262        }
263
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        };
299    }
300}
301