Home | History | Annotate | Download | only in settings
      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
     66     protected int getMetricsCategory() {
     67         return MetricsEvent.USER_CREDENTIALS;
     68     }
     69 
     70     @Override
     71     public void onResume() {
     72         super.onResume();
     73         refreshItems();
     74     }
     75 
     76     @Override
     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
     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 
     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 
    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
    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 
    160             public RemoveCredentialsTask(Context context, Fragment targetFragment) {
    161                 this.context = context;
    162                 this.targetFragment = targetFragment;
    163             }
    164 
    165             @Override
    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
    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
    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
    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> {
    234         public CredentialAdapter(Context context, int resource,  Credential[] objects) {
    235             super(context, resource, objects);
    236         }
    237 
    238         @Override
    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 
    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 
    287         Credential(final String alias) {
    288             this.alias = alias;
    289         }
    290 
    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 
    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 
    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