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 getMetricsCategory()66 protected int getMetricsCategory() { 67 return MetricsEvent.USER_CREDENTIALS; 68 } 69 70 @Override onResume()71 public void onResume() { 72 super.onResume(); 73 refreshItems(); 74 } 75 76 @Override onCreateView( LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)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 onItemClick(AdapterView<?> parent, View view, int position, long id)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 refreshItems()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 show(Fragment target, Credential item)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 onCreateDialog(Bundle savedInstanceState)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 RemoveCredentialsTask(Context context, Fragment targetFragment)160 public RemoveCredentialsTask(Context context, Fragment targetFragment) { 161 this.context = context; 162 this.targetFragment = targetFragment; 163 } 164 165 @Override doInBackground(String... aliases)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 onPostExecute(Void result)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 doInBackground(Void... params)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 onPostExecute(SortedMap<String, Credential> credentials)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> { CredentialAdapter(Context context, int resource, Credential[] objects)234 public CredentialAdapter(Context context, int resource, Credential[] objects) { 235 super(context, resource, objects); 236 } 237 238 @Override getView(int position, View view, ViewGroup parent)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 Type(String prefix)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 Credential(final String alias)287 Credential(final String alias) { 288 this.alias = alias; 289 } 290 Credential(Parcel in)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 writeToParcel(Parcel out, int flags)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 describeContents()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