Home | History | Annotate | Download | only in settings
      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;
     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         }
     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         }
     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         }
    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         }
    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         }
    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 
    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 
    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;
    173         private TrustedCertificateAdapter(Tab tab) {
    174             mTab = tab;
    175             load();
    176         }
    177         private void load() {
    178             new AliasLoader().execute();
    179         }
    180         @Override public int getCount() {
    181             return mCertHolders.size();
    182         }
    183         @Override public CertHolder getItem(int position) {
    184             return mCertHolders.get(position);
    185         }
    186         @Override public long getItemId(int position) {
    187             return position;
    188         }
    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;
    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             }
    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             }
    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             }
    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 
    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         }
    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         }
    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         }
    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 
    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;
    383         private AliasOperation(CertHolder certHolder) {
    384             mCertHolder = certHolder;
    385         }
    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         }
    413         @Override protected void onPostExecute(Boolean ok) {
    414             mCertHolder.mTab.postOperationUpdate(ok, mCertHolder);
    415         }
    416     }
    417 }
    418