UserCredentialsSettings.java revision 0708d9e119da4c4d9424c0bc54fa458d01856bd7
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 17package com.android.settings; 18 19import android.app.AlertDialog; 20import android.app.Dialog; 21import android.app.DialogFragment; 22import android.app.Fragment; 23import android.content.Context; 24import android.content.DialogInterface; 25import android.os.AsyncTask; 26import android.os.Bundle; 27import android.os.Parcel; 28import android.os.Parcelable; 29import android.os.RemoteException; 30import android.security.Credentials; 31import android.security.IKeyChainService; 32import android.security.KeyChain; 33import android.security.KeyChain.KeyChainConnection; 34import android.security.KeyStore; 35import android.util.Log; 36import android.view.LayoutInflater; 37import android.view.View; 38import android.view.ViewGroup; 39import android.widget.AdapterView; 40import android.widget.AdapterView.OnItemClickListener; 41import android.widget.ArrayAdapter; 42import android.widget.ListView; 43import android.widget.TextView; 44 45import com.android.internal.logging.MetricsProto.MetricsEvent; 46 47import java.util.EnumSet; 48import java.util.SortedMap; 49import java.util.TreeMap; 50 51import static android.view.View.GONE; 52import static android.view.View.VISIBLE; 53 54public class UserCredentialsSettings extends OptionsMenuFragment implements OnItemClickListener { 55 private static final String TAG = "UserCredentialsSettings"; 56 57 private View mRootView; 58 private ListView mListView; 59 60 @Override 61 protected int getMetricsCategory() { 62 return MetricsEvent.USER_CREDENTIALS; 63 } 64 65 @Override 66 public void onResume() { 67 super.onResume(); 68 refreshItems(); 69 } 70 71 @Override 72 public View onCreateView( 73 LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) { 74 mRootView = inflater.inflate(R.layout.user_credentials, parent, false); 75 76 // Set up an OnItemClickListener for the credential list. 77 mListView = (ListView) mRootView.findViewById(R.id.credential_list); 78 mListView.setOnItemClickListener(this); 79 80 return mRootView; 81 } 82 83 @Override 84 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 85 final Credential item = (Credential) parent.getItemAtPosition(position); 86 CredentialDialogFragment.show(this, item); 87 } 88 89 protected void refreshItems() { 90 if (isAdded()) { 91 new AliasLoader().execute(); 92 } 93 } 94 95 public static class CredentialDialogFragment extends DialogFragment { 96 private static final String TAG = "CredentialDialogFragment"; 97 private static final String ARG_CREDENTIAL = "credential"; 98 99 public static void show(Fragment target, Credential item) { 100 final Bundle args = new Bundle(); 101 args.putParcelable(ARG_CREDENTIAL, item); 102 103 final CredentialDialogFragment frag = new CredentialDialogFragment(); 104 frag.setTargetFragment(target, /* requestCode */ -1); 105 frag.setArguments(args); 106 frag.show(target.getFragmentManager(), TAG); 107 } 108 109 @Override 110 public Dialog onCreateDialog(Bundle savedInstanceState) { 111 final Credential item = (Credential) getArguments().getParcelable(ARG_CREDENTIAL); 112 View root = getActivity().getLayoutInflater() 113 .inflate(R.layout.user_credential_dialog, null); 114 ViewGroup infoContainer = (ViewGroup) root.findViewById(R.id.credential_container); 115 View view = new CredentialAdapter(getActivity(), R.layout.user_credential, 116 new Credential[] {item}).getView(0, null, null); 117 infoContainer.addView(view); 118 119 return new AlertDialog.Builder(getActivity()) 120 .setView(root) 121 .setTitle(R.string.user_credential_title) 122 .setPositiveButton(R.string.done, null) 123 .setNegativeButton(R.string.trusted_credentials_remove_label, 124 new DialogInterface.OnClickListener() { 125 @Override public void onClick(DialogInterface dialog, int id) { 126 new RemoveCredentialsTask(getContext(), getTargetFragment()) 127 .execute(item.alias); 128 dialog.dismiss(); 129 } 130 }) 131 .create(); 132 } 133 134 private class RemoveCredentialsTask extends AsyncTask<String, Void, Void> { 135 private Context context; 136 private Fragment targetFragment; 137 138 public RemoveCredentialsTask(Context context, Fragment targetFragment) { 139 this.context = context; 140 this.targetFragment = targetFragment; 141 } 142 143 @Override 144 protected Void doInBackground(String... aliases) { 145 try { 146 final KeyChainConnection conn = KeyChain.bind(getContext()); 147 try { 148 IKeyChainService keyChain = conn.getService(); 149 for (String alias : aliases) { 150 keyChain.removeKeyPair(alias); 151 } 152 } catch (RemoteException e) { 153 Log.w(TAG, "Removing credentials", e); 154 } finally { 155 conn.close(); 156 } 157 } catch (InterruptedException e) { 158 Log.w(TAG, "Connecting to keychain", e); 159 } 160 return null; 161 } 162 163 @Override 164 protected void onPostExecute(Void result) { 165 if (targetFragment instanceof UserCredentialsSettings) { 166 ((UserCredentialsSettings) targetFragment).refreshItems(); 167 } 168 } 169 } 170 } 171 172 /** 173 * Opens a background connection to KeyStore to list user credentials. 174 * The credentials are stored in a {@link CredentialAdapter} attached to the main 175 * {@link ListView} in the fragment. 176 */ 177 private class AliasLoader extends AsyncTask<Void, Void, SortedMap<String, Credential>> { 178 @Override 179 protected SortedMap<String, Credential> doInBackground(Void... params) { 180 // Create a list of names for credential sets, ordered by name. 181 SortedMap<String, Credential> credentials = new TreeMap<>(); 182 KeyStore keyStore = KeyStore.getInstance(); 183 for (final Credential.Type type : Credential.Type.values()) { 184 for (final String alias : keyStore.list(type.prefix)) { 185 Credential c = credentials.get(alias); 186 if (c == null) { 187 credentials.put(alias, (c = new Credential(alias))); 188 } 189 c.storedTypes.add(type); 190 } 191 } 192 return credentials; 193 } 194 195 @Override 196 protected void onPostExecute(SortedMap<String, Credential> credentials) { 197 // Convert the results to an array and present them using an ArrayAdapter. 198 mListView.setAdapter(new CredentialAdapter(getContext(), R.layout.user_credential, 199 credentials.values().toArray(new Credential[0]))); 200 } 201 } 202 203 /** 204 * Helper class to display {@link Credential}s in a list. 205 */ 206 private static class CredentialAdapter extends ArrayAdapter<Credential> { 207 public CredentialAdapter(Context context, int resource, Credential[] objects) { 208 super(context, resource, objects); 209 } 210 211 @Override 212 public View getView(int position, View view, ViewGroup parent) { 213 if (view == null) { 214 view = LayoutInflater.from(getContext()) 215 .inflate(R.layout.user_credential, parent, false); 216 } 217 Credential item = getItem(position); 218 ((TextView) view.findViewById(R.id.alias)).setText(item.alias); 219 view.findViewById(R.id.contents_userkey).setVisibility( 220 item.storedTypes.contains(Credential.Type.USER_PRIVATE_KEY) ? VISIBLE : GONE); 221 view.findViewById(R.id.contents_usercrt).setVisibility( 222 item.storedTypes.contains(Credential.Type.USER_CERTIFICATE) ? VISIBLE : GONE); 223 view.findViewById(R.id.contents_cacrt).setVisibility( 224 item.storedTypes.contains(Credential.Type.CA_CERTIFICATE) ? VISIBLE : GONE); 225 return view; 226 } 227 } 228 229 static class Credential implements Parcelable { 230 static enum Type { 231 CA_CERTIFICATE (Credentials.CA_CERTIFICATE), 232 USER_CERTIFICATE (Credentials.USER_CERTIFICATE), 233 USER_PRIVATE_KEY (Credentials.USER_PRIVATE_KEY), 234 USER_SECRET_KEY (Credentials.USER_SECRET_KEY); 235 236 final String prefix; 237 238 Type(String prefix) { 239 this.prefix = prefix; 240 } 241 } 242 243 /** 244 * Main part of the credential's alias. To fetch an item from KeyStore, prepend one of the 245 * prefixes from {@link CredentialItem.storedTypes}. 246 */ 247 final String alias; 248 249 /** 250 * Should contain some non-empty subset of: 251 * <ul> 252 * <li>{@link Credentials.CA_CERTIFICATE}</li> 253 * <li>{@link Credentials.USER_CERTIFICATE}</li> 254 * <li>{@link Credentials.USER_PRIVATE_KEY}</li> 255 * <li>{@link Credentials.USER_SECRET_KEY}</li> 256 * </ul> 257 */ 258 final EnumSet<Type> storedTypes = EnumSet.noneOf(Type.class); 259 260 Credential(final String alias) { 261 this.alias = alias; 262 } 263 264 Credential(Parcel in) { 265 this(in.readString()); 266 267 long typeBits = in.readLong(); 268 for (Type i : Type.values()) { 269 if ((typeBits & (1L << i.ordinal())) != 0L) { 270 storedTypes.add(i); 271 } 272 } 273 } 274 275 public void writeToParcel(Parcel out, int flags) { 276 out.writeString(alias); 277 278 long typeBits = 0; 279 for (Type i : storedTypes) { 280 typeBits |= 1L << i.ordinal(); 281 } 282 out.writeLong(typeBits); 283 } 284 285 public int describeContents() { 286 return 0; 287 } 288 289 public static final Parcelable.Creator<Credential> CREATOR 290 = new Parcelable.Creator<Credential>() { 291 public Credential createFromParcel(Parcel in) { 292 return new Credential(in); 293 } 294 295 public Credential[] newArray(int size) { 296 return new Credential[size]; 297 } 298 }; 299 } 300} 301