• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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.Fragment;
22 import android.content.DialogInterface;
23 import android.net.http.SslCertificate;
24 import android.os.AsyncTask;
25 import android.os.Bundle;
26 import android.os.RemoteException;
27 import android.security.IKeyChainService;
28 import android.security.KeyChain;
29 import android.security.KeyChain.KeyChainConnection;
30 import android.view.LayoutInflater;
31 import android.view.View;
32 import android.view.ViewGroup;
33 import android.widget.AdapterView;
34 import android.widget.BaseAdapter;
35 import android.widget.Button;
36 import android.widget.CheckBox;
37 import android.widget.FrameLayout;
38 import android.widget.ListView;
39 import android.widget.ProgressBar;
40 import android.widget.TabHost;
41 import android.widget.TextView;
42 import java.security.cert.CertificateEncodingException;
43 import java.security.cert.X509Certificate;
44 import java.util.ArrayList;
45 import java.util.Collections;
46 import java.util.List;
47 import java.util.Set;
48 import org.apache.harmony.xnet.provider.jsse.TrustedCertificateStore;
49 
50 public class TrustedCredentialsSettings extends Fragment {
51 
52     private static final String TAG = "TrustedCredentialsSettings";
53 
54     private enum Tab {
55         SYSTEM("system",
56                R.string.trusted_credentials_system_tab,
57                R.id.system_tab,
58                R.id.system_progress,
59                R.id.system_list,
60                true),
61         USER("user",
62              R.string.trusted_credentials_user_tab,
63              R.id.user_tab,
64              R.id.user_progress,
65              R.id.user_list,
66              false);
67 
68         private final String mTag;
69         private final int mLabel;
70         private final int mView;
71         private final int mProgress;
72         private final int mList;
73         private final boolean mCheckbox;
Tab(String tag, int label, int view, int progress, int list, boolean checkbox)74         private Tab(String tag, int label, int view, int progress, int list, boolean checkbox) {
75             mTag = tag;
76             mLabel = label;
77             mView = view;
78             mProgress = progress;
79             mList = list;
80             mCheckbox = checkbox;
81         }
getAliases(TrustedCertificateStore store)82         private Set<String> getAliases(TrustedCertificateStore store) {
83             switch (this) {
84                 case SYSTEM:
85                     return store.allSystemAliases();
86                 case USER:
87                     return store.userAliases();
88             }
89             throw new AssertionError();
90         }
deleted(TrustedCertificateStore store, String alias)91         private boolean deleted(TrustedCertificateStore store, String alias) {
92             switch (this) {
93                 case SYSTEM:
94                     return !store.containsAlias(alias);
95                 case USER:
96                     return false;
97             }
98             throw new AssertionError();
99         }
getButtonLabel(CertHolder certHolder)100         private int getButtonLabel(CertHolder certHolder) {
101             switch (this) {
102                 case SYSTEM:
103                     if (certHolder.mDeleted) {
104                         return R.string.trusted_credentials_enable_label;
105                     }
106                     return R.string.trusted_credentials_disable_label;
107                 case USER:
108                     return R.string.trusted_credentials_remove_label;
109             }
110             throw new AssertionError();
111         }
getButtonConfirmation(CertHolder certHolder)112         private int getButtonConfirmation(CertHolder certHolder) {
113             switch (this) {
114                 case SYSTEM:
115                     if (certHolder.mDeleted) {
116                         return R.string.trusted_credentials_enable_confirmation;
117                     }
118                     return R.string.trusted_credentials_disable_confirmation;
119                 case USER:
120                     return R.string.trusted_credentials_remove_confirmation;
121             }
122             throw new AssertionError();
123         }
postOperationUpdate(boolean ok, CertHolder certHolder)124         private void postOperationUpdate(boolean ok, CertHolder certHolder) {
125             if (ok) {
126                 if (certHolder.mTab.mCheckbox) {
127                     certHolder.mDeleted = !certHolder.mDeleted;
128                 } else {
129                     certHolder.mAdapter.mCertHolders.remove(certHolder);
130                 }
131                 certHolder.mAdapter.notifyDataSetChanged();
132             } else {
133                 // bail, reload to reset to known state
134                 certHolder.mAdapter.load();
135             }
136         }
137     }
138 
139     // be careful not to use this on the UI thread since it is does file operations
140     private final TrustedCertificateStore mStore = new TrustedCertificateStore();
141 
142     private TabHost mTabHost;
143 
onCreateView( LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)144     @Override public View onCreateView(
145             LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
146         mTabHost = (TabHost) inflater.inflate(R.layout.trusted_credentials, parent, false);
147         mTabHost.setup();
148         addTab(Tab.SYSTEM);
149         // TODO add Install button on Tab.USER to go to CertInstaller like KeyChainActivity
150         addTab(Tab.USER);
151         return mTabHost;
152     }
153 
addTab(Tab tab)154     private void addTab(Tab tab) {
155         TabHost.TabSpec systemSpec = mTabHost.newTabSpec(tab.mTag)
156                 .setIndicator(getActivity().getString(tab.mLabel))
157                 .setContent(tab.mView);
158         mTabHost.addTab(systemSpec);
159 
160         ListView lv = (ListView) mTabHost.findViewById(tab.mList);
161         final TrustedCertificateAdapter adapter = new TrustedCertificateAdapter(tab);
162         lv.setAdapter(adapter);
163         lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
164             @Override public void onItemClick(AdapterView<?> parent, View view, int pos, long id) {
165                 showCertDialog(adapter.getItem(pos));
166             }
167         });
168     }
169 
170     private class TrustedCertificateAdapter extends BaseAdapter {
171         private final List<CertHolder> mCertHolders = new ArrayList<CertHolder>();
172         private final Tab mTab;
TrustedCertificateAdapter(Tab tab)173         private TrustedCertificateAdapter(Tab tab) {
174             mTab = tab;
175             load();
176         }
load()177         private void load() {
178             new AliasLoader().execute();
179         }
getCount()180         @Override public int getCount() {
181             return mCertHolders.size();
182         }
getItem(int position)183         @Override public CertHolder getItem(int position) {
184             return mCertHolders.get(position);
185         }
getItemId(int position)186         @Override public long getItemId(int position) {
187             return position;
188         }
getView(int position, View view, ViewGroup parent)189         @Override public View getView(int position, View view, ViewGroup parent) {
190             ViewHolder holder;
191             if (view == null) {
192                 LayoutInflater inflater = LayoutInflater.from(getActivity());
193                 view = inflater.inflate(R.layout.trusted_credential, parent, false);
194                 holder = new ViewHolder();
195                 holder.mSubjectPrimaryView = (TextView)
196                         view.findViewById(R.id.trusted_credential_subject_primary);
197                 holder.mSubjectSecondaryView = (TextView)
198                         view.findViewById(R.id.trusted_credential_subject_secondary);
199                 holder.mCheckBox = (CheckBox) view.findViewById(R.id.trusted_credential_status);
200                 view.setTag(holder);
201             } else {
202                 holder = (ViewHolder) view.getTag();
203             }
204             CertHolder certHolder = mCertHolders.get(position);
205             holder.mSubjectPrimaryView.setText(certHolder.mSubjectPrimary);
206             holder.mSubjectSecondaryView.setText(certHolder.mSubjectSecondary);
207             if (mTab.mCheckbox) {
208                 holder.mCheckBox.setChecked(!certHolder.mDeleted);
209                 holder.mCheckBox.setVisibility(View.VISIBLE);
210             }
211             return view;
212         };
213 
214         private class AliasLoader extends AsyncTask<Void, Integer, List<CertHolder>> {
215             ProgressBar mProgressBar;
216             View mList;
onPreExecute()217             @Override protected void onPreExecute() {
218                 View content = mTabHost.getTabContentView();
219                 mProgressBar = (ProgressBar) content.findViewById(mTab.mProgress);
220                 mList = content.findViewById(mTab.mList);
221                 mProgressBar.setVisibility(View.VISIBLE);
222                 mList.setVisibility(View.GONE);
223             }
doInBackground(Void... params)224             @Override protected List<CertHolder> doInBackground(Void... params) {
225                 Set<String> aliases = mTab.getAliases(mStore);
226                 int max = aliases.size();
227                 int progress = 0;
228                 List<CertHolder> certHolders = new ArrayList<CertHolder>(max);
229                 for (String alias : aliases) {
230                     X509Certificate cert = (X509Certificate) mStore.getCertificate(alias, true);
231                     certHolders.add(new CertHolder(mStore,
232                                                    TrustedCertificateAdapter.this,
233                                                    mTab,
234                                                    alias,
235                                                    cert));
236                     publishProgress(++progress, max);
237                 }
238                 Collections.sort(certHolders);
239                 return certHolders;
240             }
onProgressUpdate(Integer... progressAndMax)241             @Override protected void onProgressUpdate(Integer... progressAndMax) {
242                 int progress = progressAndMax[0];
243                 int max = progressAndMax[1];
244                 if (max != mProgressBar.getMax()) {
245                     mProgressBar.setMax(max);
246                 }
247                 mProgressBar.setProgress(progress);
248             }
onPostExecute(List<CertHolder> certHolders)249             @Override protected void onPostExecute(List<CertHolder> certHolders) {
250                 mCertHolders.clear();
251                 mCertHolders.addAll(certHolders);
252                 notifyDataSetChanged();
253                 View content = mTabHost.getTabContentView();
254                 mProgressBar.setVisibility(View.GONE);
255                 mList.setVisibility(View.VISIBLE);
256                 mProgressBar.setProgress(0);
257             }
258         }
259     }
260 
261     private static class CertHolder implements Comparable<CertHolder> {
262         private final TrustedCertificateStore mStore;
263         private final TrustedCertificateAdapter mAdapter;
264         private final Tab mTab;
265         private final String mAlias;
266         private final X509Certificate mX509Cert;
267 
268         private final SslCertificate mSslCert;
269         private final String mSubjectPrimary;
270         private final String mSubjectSecondary;
271         private boolean mDeleted;
272 
CertHolder(TrustedCertificateStore store, TrustedCertificateAdapter adapter, Tab tab, String alias, X509Certificate x509Cert)273         private CertHolder(TrustedCertificateStore store,
274                            TrustedCertificateAdapter adapter,
275                            Tab tab,
276                            String alias,
277                            X509Certificate x509Cert) {
278             mStore = store;
279             mAdapter = adapter;
280             mTab = tab;
281             mAlias = alias;
282             mX509Cert = x509Cert;
283 
284             mSslCert = new SslCertificate(x509Cert);
285 
286             String cn = mSslCert.getIssuedTo().getCName();
287             String o = mSslCert.getIssuedTo().getOName();
288             String ou = mSslCert.getIssuedTo().getUName();
289             // if we have a O, use O as primary subject, secondary prefer CN over OU
290             // if we don't have an O, use CN as primary, empty secondary
291             // if we don't have O or CN, use DName as primary, empty secondary
292             if (!o.isEmpty()) {
293                 if (!cn.isEmpty()) {
294                     mSubjectPrimary = o;
295                     mSubjectSecondary = cn;
296                 } else {
297                     mSubjectPrimary = o;
298                     mSubjectSecondary = ou;
299                 }
300             } else {
301                 if (!cn.isEmpty()) {
302                     mSubjectPrimary = cn;
303                     mSubjectSecondary = "";
304                 } else {
305                     mSubjectPrimary = mSslCert.getIssuedTo().getDName();
306                     mSubjectSecondary = "";
307                 }
308             }
309             mDeleted = mTab.deleted(mStore, mAlias);
310         }
compareTo(CertHolder o)311         @Override public int compareTo(CertHolder o) {
312             int primary = this.mSubjectPrimary.compareToIgnoreCase(o.mSubjectPrimary);
313             if (primary != 0) {
314                 return primary;
315             }
316             return this.mSubjectSecondary.compareToIgnoreCase(o.mSubjectSecondary);
317         }
equals(Object o)318         @Override public boolean equals(Object o) {
319             if (!(o instanceof CertHolder)) {
320                 return false;
321             }
322             CertHolder other = (CertHolder) o;
323             return mAlias.equals(other.mAlias);
324         }
hashCode()325         @Override public int hashCode() {
326             return mAlias.hashCode();
327         }
328     }
329 
330     private static class ViewHolder {
331         private TextView mSubjectPrimaryView;
332         private TextView mSubjectSecondaryView;
333         private CheckBox mCheckBox;
334     }
335 
showCertDialog(final CertHolder certHolder)336     private void showCertDialog(final CertHolder certHolder) {
337         View view = certHolder.mSslCert.inflateCertificateView(getActivity());
338         AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
339         builder.setTitle(com.android.internal.R.string.ssl_certificate);
340         builder.setView(view);
341         builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
342             @Override public void onClick(DialogInterface dialog, int id) {
343                 dialog.dismiss();
344             }
345         });
346         final Dialog certDialog = builder.create();
347 
348         ViewGroup body = (ViewGroup) view.findViewById(com.android.internal.R.id.body);
349         LayoutInflater inflater = LayoutInflater.from(getActivity());
350         Button removeButton = (Button) inflater.inflate(R.layout.trusted_credential_details,
351                                                         body,
352                                                         false);
353         body.addView(removeButton);
354         removeButton.setText(certHolder.mTab.getButtonLabel(certHolder));
355         removeButton.setOnClickListener(new View.OnClickListener() {
356             @Override public void onClick(View v) {
357                 AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
358                 builder.setMessage(certHolder.mTab.getButtonConfirmation(certHolder));
359                 builder.setPositiveButton(
360                         android.R.string.yes, new DialogInterface.OnClickListener() {
361                     @Override public void onClick(DialogInterface dialog, int id) {
362                         new AliasOperation(certHolder).execute();
363                         dialog.dismiss();
364                         certDialog.dismiss();
365                     }
366                 });
367                 builder.setNegativeButton(
368                         android.R.string.no, new DialogInterface.OnClickListener() {
369                     @Override public void onClick(DialogInterface dialog, int id) {
370                         dialog.cancel();
371                     }
372                 });
373                 AlertDialog alert = builder.create();
374                 alert.show();
375             }
376         });
377 
378         certDialog.show();
379     }
380 
381     private class AliasOperation extends AsyncTask<Void, Void, Boolean> {
382         private final CertHolder mCertHolder;
AliasOperation(CertHolder certHolder)383         private AliasOperation(CertHolder certHolder) {
384             mCertHolder = certHolder;
385         }
doInBackground(Void... params)386         @Override protected Boolean doInBackground(Void... params) {
387             try {
388                 KeyChainConnection keyChainConnection = KeyChain.bind(getActivity());
389                 IKeyChainService service = keyChainConnection.getService();
390                 try {
391                     if (mCertHolder.mDeleted) {
392                         byte[] bytes = mCertHolder.mX509Cert.getEncoded();
393                         service.installCaCertificate(bytes);
394                         return true;
395                     } else {
396                         return service.deleteCaCertificate(mCertHolder.mAlias);
397                     }
398                 } finally {
399                     keyChainConnection.close();
400                 }
401             } catch (CertificateEncodingException e) {
402                 return false;
403             } catch (IllegalStateException e) {
404                 // used by installCaCertificate to report errors
405                 return false;
406             } catch (RemoteException e) {
407                 return false;
408             } catch (InterruptedException e) {
409                 Thread.currentThread().interrupt();
410                 return false;
411             }
412         }
onPostExecute(Boolean ok)413         @Override protected void onPostExecute(Boolean ok) {
414             mCertHolder.mTab.postOperationUpdate(ok, mCertHolder);
415         }
416     }
417 }
418