1 /* 2 * Copyright (C) 2019 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.car.developeroptions; 18 19 import static android.widget.LinearLayout.LayoutParams.MATCH_PARENT; 20 import static android.widget.LinearLayout.LayoutParams.WRAP_CONTENT; 21 22 import android.animation.LayoutTransition; 23 import android.annotation.UiThread; 24 import android.app.Activity; 25 import android.app.KeyguardManager; 26 import android.app.admin.DevicePolicyManager; 27 import android.app.settings.SettingsEnums; 28 import android.content.BroadcastReceiver; 29 import android.content.Context; 30 import android.content.DialogInterface; 31 import android.content.Intent; 32 import android.content.IntentFilter; 33 import android.content.pm.UserInfo; 34 import android.content.res.TypedArray; 35 import android.database.DataSetObserver; 36 import android.graphics.drawable.Drawable; 37 import android.net.http.SslCertificate; 38 import android.os.AsyncTask; 39 import android.os.Bundle; 40 import android.os.RemoteException; 41 import android.os.UserHandle; 42 import android.os.UserManager; 43 import android.security.IKeyChainService; 44 import android.security.KeyChain; 45 import android.security.KeyChain.KeyChainConnection; 46 import android.util.ArraySet; 47 import android.util.Log; 48 import android.util.SparseArray; 49 import android.view.LayoutInflater; 50 import android.view.View; 51 import android.view.ViewGroup; 52 import android.widget.AdapterView; 53 import android.widget.BaseAdapter; 54 import android.widget.BaseExpandableListAdapter; 55 import android.widget.ExpandableListView; 56 import android.widget.FrameLayout; 57 import android.widget.ImageView; 58 import android.widget.LinearLayout; 59 import android.widget.ListView; 60 import android.widget.ProgressBar; 61 import android.widget.Switch; 62 import android.widget.TabHost; 63 import android.widget.TextView; 64 65 import com.android.internal.annotations.GuardedBy; 66 import com.android.internal.app.UnlaunchableAppActivity; 67 import com.android.internal.widget.LockPatternUtils; 68 import com.android.car.developeroptions.core.InstrumentedFragment; 69 70 import java.security.cert.CertificateEncodingException; 71 import java.security.cert.X509Certificate; 72 import java.util.ArrayList; 73 import java.util.Collections; 74 import java.util.List; 75 import java.util.Set; 76 import java.util.function.IntConsumer; 77 78 public class TrustedCredentialsSettings extends InstrumentedFragment 79 implements TrustedCredentialsDialogBuilder.DelegateInterface { 80 81 public static final String ARG_SHOW_NEW_FOR_USER = "ARG_SHOW_NEW_FOR_USER"; 82 83 private static final String TAG = "TrustedCredentialsSettings"; 84 85 private UserManager mUserManager; 86 private KeyguardManager mKeyguardManager; 87 private int mTrustAllCaUserId; 88 89 private static final String SAVED_CONFIRMED_CREDENTIAL_USERS = "ConfirmedCredentialUsers"; 90 private static final String SAVED_CONFIRMING_CREDENTIAL_USER = "ConfirmingCredentialUser"; 91 private static final String USER_ACTION = "com.android.car.developeroptions.TRUSTED_CREDENTIALS_USER"; 92 private static final int REQUEST_CONFIRM_CREDENTIALS = 1; 93 94 @Override getMetricsCategory()95 public int getMetricsCategory() { 96 return SettingsEnums.TRUSTED_CREDENTIALS; 97 } 98 99 private enum Tab { 100 SYSTEM("system", 101 R.string.trusted_credentials_system_tab, 102 R.id.system_tab, 103 R.id.system_progress, 104 R.id.system_content, 105 true), 106 USER("user", 107 R.string.trusted_credentials_user_tab, 108 R.id.user_tab, 109 R.id.user_progress, 110 R.id.user_content, 111 false); 112 113 private final String mTag; 114 private final int mLabel; 115 private final int mView; 116 private final int mProgress; 117 private final int mContentView; 118 private final boolean mSwitch; 119 Tab(String tag, int label, int view, int progress, int contentView, boolean withSwitch)120 private Tab(String tag, int label, int view, int progress, int contentView, 121 boolean withSwitch) { 122 mTag = tag; 123 mLabel = label; 124 mView = view; 125 mProgress = progress; 126 mContentView = contentView; 127 mSwitch = withSwitch; 128 } 129 getAliases(IKeyChainService service)130 private List<String> getAliases(IKeyChainService service) throws RemoteException { 131 switch (this) { 132 case SYSTEM: { 133 return service.getSystemCaAliases().getList(); 134 } 135 case USER: 136 return service.getUserCaAliases().getList(); 137 } 138 throw new AssertionError(); 139 } deleted(IKeyChainService service, String alias)140 private boolean deleted(IKeyChainService service, String alias) throws RemoteException { 141 switch (this) { 142 case SYSTEM: 143 return !service.containsCaAlias(alias); 144 case USER: 145 return false; 146 } 147 throw new AssertionError(); 148 } 149 } 150 151 private TabHost mTabHost; 152 private ArrayList<GroupAdapter> mGroupAdapters = new ArrayList<>(2); 153 private AliasOperation mAliasOperation; 154 private ArraySet<Integer> mConfirmedCredentialUsers; 155 private int mConfirmingCredentialUser; 156 private IntConsumer mConfirmingCredentialListener; 157 private Set<AdapterData.AliasLoader> mAliasLoaders = new ArraySet<AdapterData.AliasLoader>(2); 158 @GuardedBy("mKeyChainConnectionByProfileId") 159 private final SparseArray<KeyChainConnection> 160 mKeyChainConnectionByProfileId = new SparseArray<KeyChainConnection>(); 161 162 private BroadcastReceiver mWorkProfileChangedReceiver = new BroadcastReceiver() { 163 164 @Override 165 public void onReceive(Context context, Intent intent) { 166 final String action = intent.getAction(); 167 if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) || 168 Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) || 169 Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) { 170 for (GroupAdapter adapter : mGroupAdapters) { 171 adapter.load(); 172 } 173 } 174 } 175 176 }; 177 178 @Override onCreate(Bundle savedInstanceState)179 public void onCreate(Bundle savedInstanceState) { 180 super.onCreate(savedInstanceState); 181 final Activity activity = getActivity(); 182 mUserManager = (UserManager) activity.getSystemService(Context.USER_SERVICE); 183 mKeyguardManager = (KeyguardManager) activity 184 .getSystemService(Context.KEYGUARD_SERVICE); 185 mTrustAllCaUserId = activity.getIntent().getIntExtra(ARG_SHOW_NEW_FOR_USER, 186 UserHandle.USER_NULL); 187 mConfirmedCredentialUsers = new ArraySet<>(2); 188 mConfirmingCredentialUser = UserHandle.USER_NULL; 189 if (savedInstanceState != null) { 190 mConfirmingCredentialUser = savedInstanceState.getInt(SAVED_CONFIRMING_CREDENTIAL_USER, 191 UserHandle.USER_NULL); 192 ArrayList<Integer> users = savedInstanceState.getIntegerArrayList( 193 SAVED_CONFIRMED_CREDENTIAL_USERS); 194 if (users != null) { 195 mConfirmedCredentialUsers.addAll(users); 196 } 197 } 198 199 mConfirmingCredentialListener = null; 200 201 IntentFilter filter = new IntentFilter(); 202 filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); 203 filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); 204 filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED); 205 activity.registerReceiver(mWorkProfileChangedReceiver, filter); 206 207 activity.setTitle(R.string.trusted_credentials); 208 } 209 210 @Override onSaveInstanceState(Bundle outState)211 public void onSaveInstanceState(Bundle outState) { 212 super.onSaveInstanceState(outState); 213 outState.putIntegerArrayList(SAVED_CONFIRMED_CREDENTIAL_USERS, new ArrayList<>( 214 mConfirmedCredentialUsers)); 215 outState.putInt(SAVED_CONFIRMING_CREDENTIAL_USER, mConfirmingCredentialUser); 216 } 217 onCreateView( LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)218 @Override public View onCreateView( 219 LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) { 220 mTabHost = (TabHost) inflater.inflate(R.layout.trusted_credentials, parent, false); 221 mTabHost.setup(); 222 addTab(Tab.SYSTEM); 223 // TODO add Install button on Tab.USER to go to CertInstaller like KeyChainActivity 224 addTab(Tab.USER); 225 if (getActivity().getIntent() != null && 226 USER_ACTION.equals(getActivity().getIntent().getAction())) { 227 mTabHost.setCurrentTabByTag(Tab.USER.mTag); 228 } 229 return mTabHost; 230 } 231 @Override onDestroy()232 public void onDestroy() { 233 getActivity().unregisterReceiver(mWorkProfileChangedReceiver); 234 for (AdapterData.AliasLoader aliasLoader : mAliasLoaders) { 235 aliasLoader.cancel(true); 236 } 237 mAliasLoaders.clear(); 238 mGroupAdapters.clear(); 239 if (mAliasOperation != null) { 240 mAliasOperation.cancel(true); 241 mAliasOperation = null; 242 } 243 closeKeyChainConnections(); 244 super.onDestroy(); 245 } 246 247 @Override onActivityResult(int requestCode, int resultCode, Intent data)248 public void onActivityResult(int requestCode, int resultCode, Intent data) { 249 if (requestCode == REQUEST_CONFIRM_CREDENTIALS) { 250 int userId = mConfirmingCredentialUser; 251 IntConsumer listener = mConfirmingCredentialListener; 252 // reset them before calling the listener because the listener may call back to start 253 // activity again. (though it should never happen.) 254 mConfirmingCredentialUser = UserHandle.USER_NULL; 255 mConfirmingCredentialListener = null; 256 if (resultCode == Activity.RESULT_OK) { 257 mConfirmedCredentialUsers.add(userId); 258 if (listener != null) { 259 listener.accept(userId); 260 } 261 } 262 } 263 } 264 closeKeyChainConnections()265 private void closeKeyChainConnections() { 266 synchronized (mKeyChainConnectionByProfileId) { 267 final int n = mKeyChainConnectionByProfileId.size(); 268 for (int i = 0; i < n; ++i) { 269 mKeyChainConnectionByProfileId.valueAt(i).close(); 270 } 271 mKeyChainConnectionByProfileId.clear(); 272 } 273 } 274 addTab(Tab tab)275 private void addTab(Tab tab) { 276 TabHost.TabSpec systemSpec = mTabHost.newTabSpec(tab.mTag) 277 .setIndicator(getActivity().getString(tab.mLabel)) 278 .setContent(tab.mView); 279 mTabHost.addTab(systemSpec); 280 281 final GroupAdapter groupAdapter = new GroupAdapter(tab); 282 mGroupAdapters.add(groupAdapter); 283 final int profilesSize = groupAdapter.getGroupCount(); 284 285 // Add a transition for non-visibility events like resizing the pane. 286 final ViewGroup contentView = (ViewGroup) mTabHost.findViewById(tab.mContentView); 287 contentView.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); 288 289 final LayoutInflater inflater = LayoutInflater.from(getActivity()); 290 for (int i = 0; i < groupAdapter.getGroupCount(); i++) { 291 final boolean isWork = groupAdapter.getUserInfoByGroup(i).isManagedProfile(); 292 final ChildAdapter adapter = groupAdapter.getChildAdapter(i); 293 294 final LinearLayout containerView = (LinearLayout) inflater 295 .inflate(R.layout.trusted_credential_list_container, contentView, false); 296 adapter.setContainerView(containerView); 297 298 adapter.showHeader(profilesSize > 1); 299 adapter.showDivider(isWork); 300 adapter.setExpandIfAvailable(profilesSize <= 2 ? true : !isWork); 301 if (isWork) { 302 contentView.addView(containerView); 303 } else { 304 contentView.addView(containerView, 0); 305 } 306 } 307 } 308 309 /** 310 * Start work challenge activity. 311 * @return true if screenlock exists 312 */ startConfirmCredential(int userId)313 private boolean startConfirmCredential(int userId) { 314 final Intent newIntent = mKeyguardManager.createConfirmDeviceCredentialIntent(null, null, 315 userId); 316 if (newIntent == null) { 317 return false; 318 } 319 mConfirmingCredentialUser = userId; 320 startActivityForResult(newIntent, REQUEST_CONFIRM_CREDENTIALS); 321 return true; 322 } 323 324 /** 325 * Adapter for expandable list view of certificates. Groups in the view correspond to profiles 326 * whereas children correspond to certificates. 327 */ 328 private class GroupAdapter extends BaseExpandableListAdapter implements 329 ExpandableListView.OnGroupClickListener, ExpandableListView.OnChildClickListener, 330 View.OnClickListener { 331 private final AdapterData mData; 332 GroupAdapter(Tab tab)333 private GroupAdapter(Tab tab) { 334 mData = new AdapterData(tab, this); 335 load(); 336 } 337 338 @Override getGroupCount()339 public int getGroupCount() { 340 return mData.mCertHoldersByUserId.size(); 341 } 342 @Override getChildrenCount(int groupPosition)343 public int getChildrenCount(int groupPosition) { 344 List<CertHolder> certHolders = mData.mCertHoldersByUserId.valueAt(groupPosition); 345 if (certHolders != null) { 346 return certHolders.size(); 347 } 348 return 0; 349 } 350 @Override getGroup(int groupPosition)351 public UserHandle getGroup(int groupPosition) { 352 return new UserHandle(mData.mCertHoldersByUserId.keyAt(groupPosition)); 353 } 354 @Override getChild(int groupPosition, int childPosition)355 public CertHolder getChild(int groupPosition, int childPosition) { 356 return mData.mCertHoldersByUserId.get(getUserIdByGroup(groupPosition)).get( 357 childPosition); 358 } 359 @Override getGroupId(int groupPosition)360 public long getGroupId(int groupPosition) { 361 return getUserIdByGroup(groupPosition); 362 } getUserIdByGroup(int groupPosition)363 private int getUserIdByGroup(int groupPosition) { 364 return mData.mCertHoldersByUserId.keyAt(groupPosition); 365 } getUserInfoByGroup(int groupPosition)366 public UserInfo getUserInfoByGroup(int groupPosition) { 367 return mUserManager.getUserInfo(getUserIdByGroup(groupPosition)); 368 } 369 @Override getChildId(int groupPosition, int childPosition)370 public long getChildId(int groupPosition, int childPosition) { 371 return childPosition; 372 } 373 @Override hasStableIds()374 public boolean hasStableIds() { 375 return false; 376 } 377 @Override getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent)378 public View getGroupView(int groupPosition, boolean isExpanded, View convertView, 379 ViewGroup parent) { 380 if (convertView == null) { 381 LayoutInflater inflater = (LayoutInflater) getActivity() 382 .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 383 convertView = Utils.inflateCategoryHeader(inflater, parent); 384 } 385 386 final TextView title = (TextView) convertView.findViewById(android.R.id.title); 387 if (getUserInfoByGroup(groupPosition).isManagedProfile()) { 388 title.setText(R.string.category_work); 389 } else { 390 title.setText(R.string.category_personal); 391 } 392 title.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_END); 393 394 return convertView; 395 } 396 @Override getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent)397 public View getChildView(int groupPosition, int childPosition, boolean isLastChild, 398 View convertView, ViewGroup parent) { 399 return getViewForCertificate(getChild(groupPosition, childPosition), mData.mTab, 400 convertView, parent); 401 } 402 @Override isChildSelectable(int groupPosition, int childPosition)403 public boolean isChildSelectable(int groupPosition, int childPosition) { 404 return true; 405 } 406 407 @Override onChildClick(ExpandableListView expandableListView, View view, int groupPosition, int childPosition, long id)408 public boolean onChildClick(ExpandableListView expandableListView, View view, 409 int groupPosition, int childPosition, long id) { 410 showCertDialog(getChild(groupPosition, childPosition)); 411 return true; 412 } 413 414 /** 415 * Called when the switch on a system certificate is clicked. This will toggle whether it 416 * is trusted as a credential. 417 */ 418 @Override onClick(View view)419 public void onClick(View view) { 420 CertHolder holder = (CertHolder) view.getTag(); 421 removeOrInstallCert(holder); 422 } 423 424 @Override onGroupClick(ExpandableListView expandableListView, View view, int groupPosition, long id)425 public boolean onGroupClick(ExpandableListView expandableListView, View view, 426 int groupPosition, long id) { 427 return !checkGroupExpandableAndStartWarningActivity(groupPosition); 428 } 429 load()430 public void load() { 431 mData.new AliasLoader().execute(); 432 } 433 remove(CertHolder certHolder)434 public void remove(CertHolder certHolder) { 435 mData.remove(certHolder); 436 } 437 setExpandableListView(ExpandableListView lv)438 public void setExpandableListView(ExpandableListView lv) { 439 lv.setAdapter(this); 440 lv.setOnGroupClickListener(this); 441 lv.setOnChildClickListener(this); 442 lv.setVisibility(View.VISIBLE); 443 } 444 getChildAdapter(int groupPosition)445 public ChildAdapter getChildAdapter(int groupPosition) { 446 return new ChildAdapter(this, groupPosition); 447 } 448 checkGroupExpandableAndStartWarningActivity(int groupPosition)449 public boolean checkGroupExpandableAndStartWarningActivity(int groupPosition) { 450 return checkGroupExpandableAndStartWarningActivity(groupPosition, true); 451 } 452 checkGroupExpandableAndStartWarningActivity(int groupPosition, boolean startActivity)453 public boolean checkGroupExpandableAndStartWarningActivity(int groupPosition, 454 boolean startActivity) { 455 final UserHandle groupUser = getGroup(groupPosition); 456 final int groupUserId = groupUser.getIdentifier(); 457 if (mUserManager.isQuietModeEnabled(groupUser)) { 458 final Intent intent = UnlaunchableAppActivity.createInQuietModeDialogIntent( 459 groupUserId); 460 if (startActivity) { 461 getActivity().startActivity(intent); 462 } 463 return false; 464 } else if (!mUserManager.isUserUnlocked(groupUser)) { 465 final LockPatternUtils lockPatternUtils = new LockPatternUtils( 466 getActivity()); 467 if (lockPatternUtils.isSeparateProfileChallengeEnabled(groupUserId)) { 468 if (startActivity) { 469 startConfirmCredential(groupUserId); 470 } 471 return false; 472 } 473 } 474 return true; 475 } 476 getViewForCertificate(CertHolder certHolder, Tab mTab, View convertView, ViewGroup parent)477 private View getViewForCertificate(CertHolder certHolder, Tab mTab, View convertView, 478 ViewGroup parent) { 479 ViewHolder holder; 480 if (convertView == null) { 481 holder = new ViewHolder(); 482 LayoutInflater inflater = LayoutInflater.from(getActivity()); 483 convertView = inflater.inflate(R.layout.trusted_credential, parent, false); 484 convertView.setTag(holder); 485 holder.mSubjectPrimaryView = (TextView) 486 convertView.findViewById(R.id.trusted_credential_subject_primary); 487 holder.mSubjectSecondaryView = (TextView) 488 convertView.findViewById(R.id.trusted_credential_subject_secondary); 489 holder.mSwitch = (Switch) convertView.findViewById( 490 R.id.trusted_credential_status); 491 holder.mSwitch.setOnClickListener(this); 492 } else { 493 holder = (ViewHolder) convertView.getTag(); 494 } 495 holder.mSubjectPrimaryView.setText(certHolder.mSubjectPrimary); 496 holder.mSubjectSecondaryView.setText(certHolder.mSubjectSecondary); 497 if (mTab.mSwitch) { 498 holder.mSwitch.setChecked(!certHolder.mDeleted); 499 holder.mSwitch.setEnabled(!mUserManager.hasUserRestriction( 500 UserManager.DISALLOW_CONFIG_CREDENTIALS, 501 new UserHandle(certHolder.mProfileId))); 502 holder.mSwitch.setVisibility(View.VISIBLE); 503 holder.mSwitch.setTag(certHolder); 504 } 505 return convertView; 506 } 507 508 private class ViewHolder { 509 private TextView mSubjectPrimaryView; 510 private TextView mSubjectSecondaryView; 511 private Switch mSwitch; 512 } 513 } 514 515 private class ChildAdapter extends BaseAdapter implements View.OnClickListener, 516 AdapterView.OnItemClickListener { 517 private final int[] GROUP_EXPANDED_STATE_SET = {com.android.internal.R.attr.state_expanded}; 518 private final int[] EMPTY_STATE_SET = {}; 519 private final LinearLayout.LayoutParams HIDE_CONTAINER_LAYOUT_PARAMS = 520 new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT, 0f); 521 private final LinearLayout.LayoutParams HIDE_LIST_LAYOUT_PARAMS = 522 new LinearLayout.LayoutParams(MATCH_PARENT, 0); 523 private final LinearLayout.LayoutParams SHOW_LAYOUT_PARAMS = new LinearLayout.LayoutParams( 524 LinearLayout.LayoutParams.MATCH_PARENT, MATCH_PARENT, 1f); 525 private final GroupAdapter mParent; 526 private final int mGroupPosition; 527 /* 528 * This class doesn't hold the actual data. Events should notify parent. 529 * When notifying DataSet events in this class, events should be forwarded to mParent. 530 * i.e. this.notifyDataSetChanged -> mParent.notifyDataSetChanged -> mObserver.onChanged 531 * -> outsideObservers.onChanged() (e.g. ListView) 532 */ 533 private final DataSetObserver mObserver = new DataSetObserver() { 534 @Override 535 public void onChanged() { 536 super.onChanged(); 537 ChildAdapter.super.notifyDataSetChanged(); 538 } 539 @Override 540 public void onInvalidated() { 541 super.onInvalidated(); 542 ChildAdapter.super.notifyDataSetInvalidated(); 543 } 544 }; 545 546 private boolean mIsListExpanded = true; 547 private LinearLayout mContainerView; 548 private ViewGroup mHeaderView; 549 private ListView mListView; 550 private ImageView mIndicatorView; 551 ChildAdapter(GroupAdapter parent, int groupPosition)552 private ChildAdapter(GroupAdapter parent, int groupPosition) { 553 mParent = parent; 554 mGroupPosition = groupPosition; 555 mParent.registerDataSetObserver(mObserver); 556 } 557 getCount()558 @Override public int getCount() { 559 return mParent.getChildrenCount(mGroupPosition); 560 } getItem(int position)561 @Override public CertHolder getItem(int position) { 562 return mParent.getChild(mGroupPosition, position); 563 } getItemId(int position)564 @Override public long getItemId(int position) { 565 return mParent.getChildId(mGroupPosition, position); 566 } getView(int position, View convertView, ViewGroup parent)567 @Override public View getView(int position, View convertView, ViewGroup parent) { 568 return mParent.getChildView(mGroupPosition, position, false, convertView, parent); 569 } 570 // DataSet events 571 @Override notifyDataSetChanged()572 public void notifyDataSetChanged() { 573 // Don't call super as the parent will propagate this event back later in mObserver 574 mParent.notifyDataSetChanged(); 575 } 576 @Override notifyDataSetInvalidated()577 public void notifyDataSetInvalidated() { 578 // Don't call super as the parent will propagate this event back later in mObserver 579 mParent.notifyDataSetInvalidated(); 580 } 581 582 // View related codes 583 @Override onClick(View view)584 public void onClick(View view) { 585 mIsListExpanded = checkGroupExpandableAndStartWarningActivity() && !mIsListExpanded; 586 refreshViews(); 587 } 588 589 @Override onItemClick(AdapterView<?> adapterView, View view, int pos, long id)590 public void onItemClick(AdapterView<?> adapterView, View view, int pos, long id) { 591 showCertDialog(getItem(pos)); 592 } 593 setContainerView(LinearLayout containerView)594 public void setContainerView(LinearLayout containerView) { 595 mContainerView = containerView; 596 597 mListView = (ListView) mContainerView.findViewById(R.id.cert_list); 598 mListView.setAdapter(this); 599 mListView.setOnItemClickListener(this); 600 mListView.setItemsCanFocus(true); 601 602 mHeaderView = (ViewGroup) mContainerView.findViewById(R.id.header_view); 603 mHeaderView.setOnClickListener(this); 604 605 mIndicatorView = (ImageView) mHeaderView.findViewById(R.id.group_indicator); 606 mIndicatorView.setImageDrawable(getGroupIndicator()); 607 608 FrameLayout headerContentContainer = (FrameLayout) 609 mHeaderView.findViewById(R.id.header_content_container); 610 headerContentContainer.addView( 611 mParent.getGroupView(mGroupPosition, true /* parent ignores it */, null, 612 headerContentContainer)); 613 } 614 showHeader(boolean showHeader)615 public void showHeader(boolean showHeader) { 616 mHeaderView.setVisibility(showHeader ? View.VISIBLE : View.GONE); 617 } 618 showDivider(boolean showDivider)619 public void showDivider(boolean showDivider) { 620 View dividerView = mHeaderView.findViewById(R.id.header_divider); 621 dividerView.setVisibility(showDivider ? View.VISIBLE : View.GONE ); 622 } 623 setExpandIfAvailable(boolean expanded)624 public void setExpandIfAvailable(boolean expanded) { 625 mIsListExpanded = expanded && mParent.checkGroupExpandableAndStartWarningActivity( 626 mGroupPosition, false /* startActivity */); 627 refreshViews(); 628 } 629 checkGroupExpandableAndStartWarningActivity()630 private boolean checkGroupExpandableAndStartWarningActivity() { 631 return mParent.checkGroupExpandableAndStartWarningActivity(mGroupPosition); 632 } 633 refreshViews()634 private void refreshViews() { 635 mIndicatorView.setImageState(mIsListExpanded ? GROUP_EXPANDED_STATE_SET 636 : EMPTY_STATE_SET, false); 637 mListView.setLayoutParams(mIsListExpanded ? SHOW_LAYOUT_PARAMS 638 : HIDE_LIST_LAYOUT_PARAMS); 639 mContainerView.setLayoutParams(mIsListExpanded ? SHOW_LAYOUT_PARAMS 640 : HIDE_CONTAINER_LAYOUT_PARAMS); 641 } 642 643 // Get group indicator from styles of ExpandableListView getGroupIndicator()644 private Drawable getGroupIndicator() { 645 final TypedArray a = getActivity().obtainStyledAttributes(null, 646 com.android.internal.R.styleable.ExpandableListView, 647 com.android.internal.R.attr.expandableListViewStyle, 0); 648 Drawable groupIndicator = a.getDrawable( 649 com.android.internal.R.styleable.ExpandableListView_groupIndicator); 650 a.recycle(); 651 return groupIndicator; 652 } 653 } 654 655 private class AdapterData { 656 private final SparseArray<List<CertHolder>> mCertHoldersByUserId = 657 new SparseArray<List<CertHolder>>(); 658 private final Tab mTab; 659 private final GroupAdapter mAdapter; 660 AdapterData(Tab tab, GroupAdapter adapter)661 private AdapterData(Tab tab, GroupAdapter adapter) { 662 mAdapter = adapter; 663 mTab = tab; 664 } 665 666 private class AliasLoader extends AsyncTask<Void, Integer, SparseArray<List<CertHolder>>> { 667 private ProgressBar mProgressBar; 668 private View mContentView; 669 private Context mContext; 670 AliasLoader()671 public AliasLoader() { 672 mContext = getActivity(); 673 mAliasLoaders.add(this); 674 List<UserHandle> profiles = mUserManager.getUserProfiles(); 675 for (UserHandle profile : profiles) { 676 mCertHoldersByUserId.put(profile.getIdentifier(), new ArrayList<CertHolder>()); 677 } 678 } 679 shouldSkipProfile(UserHandle userHandle)680 private boolean shouldSkipProfile(UserHandle userHandle) { 681 return mUserManager.isQuietModeEnabled(userHandle) 682 || !mUserManager.isUserUnlocked(userHandle.getIdentifier()); 683 } 684 onPreExecute()685 @Override protected void onPreExecute() { 686 View content = mTabHost.getTabContentView(); 687 mProgressBar = (ProgressBar) content.findViewById(mTab.mProgress); 688 mContentView = content.findViewById(mTab.mContentView); 689 mProgressBar.setVisibility(View.VISIBLE); 690 mContentView.setVisibility(View.GONE); 691 } doInBackground(Void... params)692 @Override protected SparseArray<List<CertHolder>> doInBackground(Void... params) { 693 SparseArray<List<CertHolder>> certHoldersByProfile = 694 new SparseArray<List<CertHolder>>(); 695 try { 696 synchronized(mKeyChainConnectionByProfileId) { 697 List<UserHandle> profiles = mUserManager.getUserProfiles(); 698 final int n = profiles.size(); 699 // First we get all aliases for all profiles in order to show progress 700 // correctly. Otherwise this could all be in a single loop. 701 SparseArray<List<String>> aliasesByProfileId = new SparseArray< 702 List<String>>(n); 703 int max = 0; 704 int progress = 0; 705 for (int i = 0; i < n; ++i) { 706 UserHandle profile = profiles.get(i); 707 int profileId = profile.getIdentifier(); 708 if (shouldSkipProfile(profile)) { 709 continue; 710 } 711 KeyChainConnection keyChainConnection = KeyChain.bindAsUser(mContext, 712 profile); 713 // Saving the connection for later use on the certificate dialog. 714 mKeyChainConnectionByProfileId.put(profileId, keyChainConnection); 715 IKeyChainService service = keyChainConnection.getService(); 716 List<String> aliases = mTab.getAliases(service); 717 if (isCancelled()) { 718 return new SparseArray<List<CertHolder>>(); 719 } 720 max += aliases.size(); 721 aliasesByProfileId.put(profileId, aliases); 722 } 723 for (int i = 0; i < n; ++i) { 724 UserHandle profile = profiles.get(i); 725 int profileId = profile.getIdentifier(); 726 List<String> aliases = aliasesByProfileId.get(profileId); 727 if (isCancelled()) { 728 return new SparseArray<List<CertHolder>>(); 729 } 730 KeyChainConnection keyChainConnection = mKeyChainConnectionByProfileId.get( 731 profileId); 732 if (shouldSkipProfile(profile) || aliases == null 733 || keyChainConnection == null) { 734 certHoldersByProfile.put(profileId, new ArrayList<CertHolder>(0)); 735 continue; 736 } 737 IKeyChainService service = keyChainConnection.getService(); 738 List<CertHolder> certHolders = new ArrayList<CertHolder>(max); 739 final int aliasMax = aliases.size(); 740 for (int j = 0; j < aliasMax; ++j) { 741 String alias = aliases.get(j); 742 byte[] encodedCertificate = service.getEncodedCaCertificate(alias, 743 true); 744 X509Certificate cert = KeyChain.toCertificate(encodedCertificate); 745 certHolders.add(new CertHolder(service, mAdapter, 746 mTab, alias, cert, profileId)); 747 publishProgress(++progress, max); 748 } 749 Collections.sort(certHolders); 750 certHoldersByProfile.put(profileId, certHolders); 751 } 752 return certHoldersByProfile; 753 } 754 } catch (RemoteException e) { 755 Log.e(TAG, "Remote exception while loading aliases.", e); 756 return new SparseArray<List<CertHolder>>(); 757 } catch (InterruptedException e) { 758 Log.e(TAG, "InterruptedException while loading aliases.", e); 759 return new SparseArray<List<CertHolder>>(); 760 } 761 } onProgressUpdate(Integer... progressAndMax)762 @Override protected void onProgressUpdate(Integer... progressAndMax) { 763 int progress = progressAndMax[0]; 764 int max = progressAndMax[1]; 765 if (max != mProgressBar.getMax()) { 766 mProgressBar.setMax(max); 767 } 768 mProgressBar.setProgress(progress); 769 } onPostExecute(SparseArray<List<CertHolder>> certHolders)770 @Override protected void onPostExecute(SparseArray<List<CertHolder>> certHolders) { 771 mCertHoldersByUserId.clear(); 772 final int n = certHolders.size(); 773 for (int i = 0; i < n; ++i) { 774 mCertHoldersByUserId.put(certHolders.keyAt(i), certHolders.valueAt(i)); 775 } 776 mAdapter.notifyDataSetChanged(); 777 mProgressBar.setVisibility(View.GONE); 778 mContentView.setVisibility(View.VISIBLE); 779 mProgressBar.setProgress(0); 780 mAliasLoaders.remove(this); 781 showTrustAllCaDialogIfNeeded(); 782 } 783 isUserTabAndTrustAllCertMode()784 private boolean isUserTabAndTrustAllCertMode() { 785 return isTrustAllCaCertModeInProgress() && mTab == Tab.USER; 786 } 787 788 @UiThread showTrustAllCaDialogIfNeeded()789 private void showTrustAllCaDialogIfNeeded() { 790 if (!isUserTabAndTrustAllCertMode()) { 791 return; 792 } 793 List<CertHolder> certHolders = mCertHoldersByUserId.get(mTrustAllCaUserId); 794 if (certHolders == null) { 795 return; 796 } 797 798 List<CertHolder> unapprovedUserCertHolders = new ArrayList<>(); 799 final DevicePolicyManager dpm = mContext.getSystemService( 800 DevicePolicyManager.class); 801 for (CertHolder cert : certHolders) { 802 if (cert != null && !dpm.isCaCertApproved(cert.mAlias, mTrustAllCaUserId)) { 803 unapprovedUserCertHolders.add(cert); 804 } 805 } 806 807 if (unapprovedUserCertHolders.size() == 0) { 808 Log.w(TAG, "no cert is pending approval for user " + mTrustAllCaUserId); 809 return; 810 } 811 showTrustAllCaDialog(unapprovedUserCertHolders); 812 } 813 } 814 remove(CertHolder certHolder)815 public void remove(CertHolder certHolder) { 816 if (mCertHoldersByUserId != null) { 817 final List<CertHolder> certs = mCertHoldersByUserId.get(certHolder.mProfileId); 818 if (certs != null) { 819 certs.remove(certHolder); 820 } 821 } 822 } 823 } 824 825 /* package */ static class CertHolder implements Comparable<CertHolder> { 826 public int mProfileId; 827 private final IKeyChainService mService; 828 private final GroupAdapter mAdapter; 829 private final Tab mTab; 830 private final String mAlias; 831 private final X509Certificate mX509Cert; 832 833 private final SslCertificate mSslCert; 834 private final String mSubjectPrimary; 835 private final String mSubjectSecondary; 836 private boolean mDeleted; 837 CertHolder(IKeyChainService service, GroupAdapter adapter, Tab tab, String alias, X509Certificate x509Cert, int profileId)838 private CertHolder(IKeyChainService service, 839 GroupAdapter adapter, 840 Tab tab, 841 String alias, 842 X509Certificate x509Cert, 843 int profileId) { 844 mProfileId = profileId; 845 mService = service; 846 mAdapter = adapter; 847 mTab = tab; 848 mAlias = alias; 849 mX509Cert = x509Cert; 850 851 mSslCert = new SslCertificate(x509Cert); 852 853 String cn = mSslCert.getIssuedTo().getCName(); 854 String o = mSslCert.getIssuedTo().getOName(); 855 String ou = mSslCert.getIssuedTo().getUName(); 856 // if we have a O, use O as primary subject, secondary prefer CN over OU 857 // if we don't have an O, use CN as primary, empty secondary 858 // if we don't have O or CN, use DName as primary, empty secondary 859 if (!o.isEmpty()) { 860 if (!cn.isEmpty()) { 861 mSubjectPrimary = o; 862 mSubjectSecondary = cn; 863 } else { 864 mSubjectPrimary = o; 865 mSubjectSecondary = ou; 866 } 867 } else { 868 if (!cn.isEmpty()) { 869 mSubjectPrimary = cn; 870 mSubjectSecondary = ""; 871 } else { 872 mSubjectPrimary = mSslCert.getIssuedTo().getDName(); 873 mSubjectSecondary = ""; 874 } 875 } 876 try { 877 mDeleted = mTab.deleted(mService, mAlias); 878 } catch (RemoteException e) { 879 Log.e(TAG, "Remote exception while checking if alias " + mAlias + " is deleted.", 880 e); 881 mDeleted = false; 882 } 883 } compareTo(CertHolder o)884 @Override public int compareTo(CertHolder o) { 885 int primary = this.mSubjectPrimary.compareToIgnoreCase(o.mSubjectPrimary); 886 if (primary != 0) { 887 return primary; 888 } 889 return this.mSubjectSecondary.compareToIgnoreCase(o.mSubjectSecondary); 890 } equals(Object o)891 @Override public boolean equals(Object o) { 892 if (!(o instanceof CertHolder)) { 893 return false; 894 } 895 CertHolder other = (CertHolder) o; 896 return mAlias.equals(other.mAlias); 897 } hashCode()898 @Override public int hashCode() { 899 return mAlias.hashCode(); 900 } 901 getUserId()902 public int getUserId() { 903 return mProfileId; 904 } 905 getAlias()906 public String getAlias() { 907 return mAlias; 908 } 909 isSystemCert()910 public boolean isSystemCert() { 911 return mTab == Tab.SYSTEM; 912 } 913 isDeleted()914 public boolean isDeleted() { 915 return mDeleted; 916 } 917 } 918 919 isTrustAllCaCertModeInProgress()920 private boolean isTrustAllCaCertModeInProgress() { 921 return mTrustAllCaUserId != UserHandle.USER_NULL; 922 } 923 showTrustAllCaDialog(List<CertHolder> unapprovedCertHolders)924 private void showTrustAllCaDialog(List<CertHolder> unapprovedCertHolders) { 925 final CertHolder[] arr = unapprovedCertHolders.toArray( 926 new CertHolder[unapprovedCertHolders.size()]); 927 new TrustedCredentialsDialogBuilder(getActivity(), this) 928 .setCertHolders(arr) 929 .setOnDismissListener(new DialogInterface.OnDismissListener() { 930 @Override 931 public void onDismiss(DialogInterface dialogInterface) { 932 // Avoid starting dialog again after Activity restart. 933 getActivity().getIntent().removeExtra(ARG_SHOW_NEW_FOR_USER); 934 mTrustAllCaUserId = UserHandle.USER_NULL; 935 } 936 }) 937 .show(); 938 } 939 showCertDialog(final CertHolder certHolder)940 private void showCertDialog(final CertHolder certHolder) { 941 new TrustedCredentialsDialogBuilder(getActivity(), this) 942 .setCertHolder(certHolder) 943 .show(); 944 } 945 946 @Override getX509CertsFromCertHolder(CertHolder certHolder)947 public List<X509Certificate> getX509CertsFromCertHolder(CertHolder certHolder) { 948 List<X509Certificate> certificates = null; 949 try { 950 synchronized (mKeyChainConnectionByProfileId) { 951 KeyChainConnection keyChainConnection = mKeyChainConnectionByProfileId.get( 952 certHolder.mProfileId); 953 IKeyChainService service = keyChainConnection.getService(); 954 List<String> chain = service.getCaCertificateChainAliases(certHolder.mAlias, true); 955 final int n = chain.size(); 956 certificates = new ArrayList<X509Certificate>(n); 957 for (int i = 0; i < n; ++i) { 958 byte[] encodedCertificate = service.getEncodedCaCertificate(chain.get(i), true); 959 X509Certificate certificate = KeyChain.toCertificate(encodedCertificate); 960 certificates.add(certificate); 961 } 962 } 963 } catch (RemoteException ex) { 964 Log.e(TAG, "RemoteException while retrieving certificate chain for root " 965 + certHolder.mAlias, ex); 966 } 967 return certificates; 968 } 969 970 @Override removeOrInstallCert(CertHolder certHolder)971 public void removeOrInstallCert(CertHolder certHolder) { 972 new AliasOperation(certHolder).execute(); 973 } 974 975 @Override startConfirmCredentialIfNotConfirmed(int userId, IntConsumer onCredentialConfirmedListener)976 public boolean startConfirmCredentialIfNotConfirmed(int userId, 977 IntConsumer onCredentialConfirmedListener) { 978 if (mConfirmedCredentialUsers.contains(userId)) { 979 // Credential has been confirmed. Don't start activity. 980 return false; 981 } 982 983 boolean result = startConfirmCredential(userId); 984 if (result) { 985 mConfirmingCredentialListener = onCredentialConfirmedListener; 986 } 987 return result; 988 } 989 990 private class AliasOperation extends AsyncTask<Void, Void, Boolean> { 991 private final CertHolder mCertHolder; 992 AliasOperation(CertHolder certHolder)993 private AliasOperation(CertHolder certHolder) { 994 mCertHolder = certHolder; 995 mAliasOperation = this; 996 } 997 998 @Override doInBackground(Void... params)999 protected Boolean doInBackground(Void... params) { 1000 try { 1001 synchronized (mKeyChainConnectionByProfileId) { 1002 KeyChainConnection keyChainConnection = mKeyChainConnectionByProfileId.get( 1003 mCertHolder.mProfileId); 1004 IKeyChainService service = keyChainConnection.getService(); 1005 if (mCertHolder.mDeleted) { 1006 byte[] bytes = mCertHolder.mX509Cert.getEncoded(); 1007 service.installCaCertificate(bytes); 1008 return true; 1009 } else { 1010 return service.deleteCaCertificate(mCertHolder.mAlias); 1011 } 1012 } 1013 } catch (CertificateEncodingException | SecurityException | IllegalStateException 1014 | RemoteException e) { 1015 Log.w(TAG, "Error while toggling alias " + mCertHolder.mAlias, e); 1016 return false; 1017 } 1018 } 1019 1020 @Override onPostExecute(Boolean ok)1021 protected void onPostExecute(Boolean ok) { 1022 if (ok) { 1023 if (mCertHolder.mTab.mSwitch) { 1024 mCertHolder.mDeleted = !mCertHolder.mDeleted; 1025 } else { 1026 mCertHolder.mAdapter.remove(mCertHolder); 1027 } 1028 mCertHolder.mAdapter.notifyDataSetChanged(); 1029 } else { 1030 // bail, reload to reset to known state 1031 mCertHolder.mAdapter.load(); 1032 } 1033 mAliasOperation = null; 1034 } 1035 } 1036 } 1037