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 
17 package com.android.settings;
18 
19 import android.app.AlertDialog;
20 import android.app.Dialog;
21 import android.app.DialogFragment;
22 import android.app.Fragment;
23 import android.content.Context;
24 import android.content.DialogInterface;
25 import android.os.AsyncTask;
26 import android.os.Bundle;
27 import android.os.Parcel;
28 import android.os.Parcelable;
29 import android.os.RemoteException;
30 import android.os.UserHandle;
31 import android.os.UserManager;
32 import android.security.Credentials;
33 import android.security.IKeyChainService;
34 import android.security.KeyChain;
35 import android.security.KeyChain.KeyChainConnection;
36 import android.security.KeyStore;
37 import android.util.Log;
38 import android.view.LayoutInflater;
39 import android.view.View;
40 import android.view.ViewGroup;
41 import android.widget.AdapterView;
42 import android.widget.AdapterView.OnItemClickListener;
43 import android.widget.ArrayAdapter;
44 import android.widget.ListView;
45 import android.widget.TextView;
46 
47 import com.android.internal.logging.MetricsProto.MetricsEvent;
48 import com.android.internal.widget.LockPatternUtils;
49 import com.android.settingslib.RestrictedLockUtils;
50 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
51 
52 import java.util.EnumSet;
53 import java.util.SortedMap;
54 import java.util.TreeMap;
55 
56 import static android.view.View.GONE;
57 import static android.view.View.VISIBLE;
58 
59 public class UserCredentialsSettings extends OptionsMenuFragment implements OnItemClickListener {
60     private static final String TAG = "UserCredentialsSettings";
61 
62     private View mRootView;
63     private ListView mListView;
64 
65     @Override
getMetricsCategory()66     protected int getMetricsCategory() {
67         return MetricsEvent.USER_CREDENTIALS;
68     }
69 
70     @Override
onResume()71     public void onResume() {
72         super.onResume();
73         refreshItems();
74     }
75 
76     @Override
onCreateView( LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)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
onItemClick(AdapterView<?> parent, View view, int position, long id)89     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
90         final Credential item = (Credential) parent.getItemAtPosition(position);
91         CredentialDialogFragment.show(this, item);
92     }
93 
refreshItems()94     protected void refreshItems() {
95         if (isAdded()) {
96             new AliasLoader().execute();
97         }
98     }
99 
100     public static class CredentialDialogFragment extends DialogFragment {
101         private static final String TAG = "CredentialDialogFragment";
102         private static final String ARG_CREDENTIAL = "credential";
103 
show(Fragment target, Credential item)104         public static void show(Fragment target, Credential item) {
105             final Bundle args = new Bundle();
106             args.putParcelable(ARG_CREDENTIAL, item);
107 
108             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             }
114         }
115 
116         @Override
onCreateDialog(Bundle savedInstanceState)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 
126             UserManager userManager
127                     = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
128 
129             AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
130                     .setView(root)
131                     .setTitle(R.string.user_credential_title)
132                     .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();
154         }
155 
156         private class RemoveCredentialsTask extends AsyncTask<String, Void, Void> {
157             private Context context;
158             private Fragment targetFragment;
159 
RemoveCredentialsTask(Context context, Fragment targetFragment)160             public RemoveCredentialsTask(Context context, Fragment targetFragment) {
161                 this.context = context;
162                 this.targetFragment = targetFragment;
163             }
164 
165             @Override
doInBackground(String... aliases)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
onPostExecute(Void result)186             protected void onPostExecute(Void result) {
187                 if (targetFragment instanceof UserCredentialsSettings) {
188                     ((UserCredentialsSettings) targetFragment).refreshItems();
189                 }
190             }
191         }
192     }
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      */
199     private class AliasLoader extends AsyncTask<Void, Void, SortedMap<String, Credential>> {
200         @Override
doInBackground(Void... params)201         protected SortedMap<String, Credential> doInBackground(Void... params) {
202             // 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)) {
207                     // 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                     }
212                     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             }
219             return credentials;
220         }
221 
222         @Override
onPostExecute(SortedMap<String, Credential> credentials)223         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])));
227         }
228     }
229 
230     /**
231      * Helper class to display {@link Credential}s in a list.
232      */
233     private static class CredentialAdapter extends ArrayAdapter<Credential> {
CredentialAdapter(Context context, int resource, Credential[] objects)234         public CredentialAdapter(Context context, int resource,  Credential[] objects) {
235             super(context, resource, objects);
236         }
237 
238         @Override
getView(int position, View view, ViewGroup parent)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 
256     static class Credential implements Parcelable {
257         static enum Type {
258             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 
Type(String prefix)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          */
285         final EnumSet<Type> storedTypes = EnumSet.noneOf(Type.class);
286 
Credential(final String alias)287         Credential(final String alias) {
288             this.alias = alias;
289         }
290 
Credential(Parcel in)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 
writeToParcel(Parcel out, int flags)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 
describeContents()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         };
326     }
327 }
328