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