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