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