blob: 80b97e4272af1dcbcf13df9bfcdb3fc2b2d13aad [file] [log] [blame]
Robin Leebaefdcf2015-08-26 10:57:44 +01001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.settings;
18
Robin Leee68d9572016-07-19 16:10:39 +010019import android.annotation.LayoutRes;
20import android.annotation.Nullable;
Robin Lee04046a12016-01-19 11:42:57 +000021import android.app.Dialog;
Fan Zhang31b21002019-01-16 13:49:47 -080022import android.app.settings.SettingsEnums;
Robin Leebaefdcf2015-08-26 10:57:44 +010023import android.content.Context;
24import android.content.DialogInterface;
25import android.os.AsyncTask;
26import android.os.Bundle;
Robin Lee04046a12016-01-19 11:42:57 +000027import android.os.Parcel;
28import android.os.Parcelable;
Robin Leee68d9572016-07-19 16:10:39 +010029import android.os.Process;
Robin Leeda7bc512016-02-24 17:39:32 +000030import android.os.RemoteException;
Robin Leec421db72016-03-11 16:22:23 +000031import android.os.UserHandle;
32import android.os.UserManager;
Robin Leebaefdcf2015-08-26 10:57:44 +010033import android.security.Credentials;
Robin Leeda7bc512016-02-24 17:39:32 +000034import android.security.IKeyChainService;
35import android.security.KeyChain;
36import android.security.KeyChain.KeyChainConnection;
Janis Danisevskisa05bd652021-01-25 14:54:48 -080037import android.security.keystore.KeyProperties;
38import android.security.keystore2.AndroidKeyStoreLoadStoreParameter;
Robin Leeda7bc512016-02-24 17:39:32 +000039import android.util.Log;
Robin Leee68d9572016-07-19 16:10:39 +010040import android.util.SparseArray;
Robin Leebaefdcf2015-08-26 10:57:44 +010041import android.view.LayoutInflater;
42import android.view.View;
43import android.view.ViewGroup;
Robin Leebaefdcf2015-08-26 10:57:44 +010044import android.widget.TextView;
45
Fan Zhang23f8d592018-08-28 15:11:40 -070046import androidx.appcompat.app.AlertDialog;
47import androidx.fragment.app.DialogFragment;
48import androidx.fragment.app.Fragment;
49import androidx.recyclerview.widget.RecyclerView;
50
Ricky Wai95792742016-05-24 19:28:53 +010051import com.android.internal.widget.LockPatternUtils;
Fan Zhang1e516282016-09-16 12:45:07 -070052import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
Robin Leec421db72016-03-11 16:22:23 +000053import com.android.settingslib.RestrictedLockUtils;
54import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
Philip P. Moltmanne3f72112018-08-28 15:01:43 -070055import com.android.settingslib.RestrictedLockUtilsInternal;
Fan Zhangc7162cd2018-06-18 15:21:41 -070056
Janis Danisevskisa05bd652021-01-25 14:54:48 -080057import java.security.Key;
58import java.security.KeyStore;
59import java.security.KeyStoreException;
60import java.security.NoSuchAlgorithmException;
Janis Danisevskisb705e1a2017-04-19 16:23:02 -070061import java.security.UnrecoverableKeyException;
Janis Danisevskisa05bd652021-01-25 14:54:48 -080062import java.security.cert.Certificate;
Robin Leee68d9572016-07-19 16:10:39 +010063import java.util.ArrayList;
Robin Leebaefdcf2015-08-26 10:57:44 +010064import java.util.EnumSet;
Janis Danisevskisa05bd652021-01-25 14:54:48 -080065import java.util.Enumeration;
Robin Leee68d9572016-07-19 16:10:39 +010066import java.util.List;
Robin Leebaefdcf2015-08-26 10:57:44 +010067import java.util.SortedMap;
68import java.util.TreeMap;
69
Janis Danisevskisa05bd652021-01-25 14:54:48 -080070import javax.crypto.SecretKey;
71
Robin Leeccaf9c92017-03-24 14:50:05 +000072public class UserCredentialsSettings extends SettingsPreferenceFragment
73 implements View.OnClickListener {
Robin Leebaefdcf2015-08-26 10:57:44 +010074 private static final String TAG = "UserCredentialsSettings";
75
Janis Danisevskis75a91832021-03-10 09:30:46 -080076 private static final String KEYSTORE_PROVIDER = "AndroidKeyStore";
77
Robin Leebaefdcf2015-08-26 10:57:44 +010078 @Override
Fan Zhang65076132016-08-08 10:25:13 -070079 public int getMetricsCategory() {
Fan Zhang31b21002019-01-16 13:49:47 -080080 return SettingsEnums.USER_CREDENTIALS;
Robin Leebaefdcf2015-08-26 10:57:44 +010081 }
82
83 @Override
84 public void onResume() {
85 super.onResume();
Robin Lee04046a12016-01-19 11:42:57 +000086 refreshItems();
Robin Leebaefdcf2015-08-26 10:57:44 +010087 }
88
89 @Override
Robin Leeccaf9c92017-03-24 14:50:05 +000090 public void onClick(final View view) {
91 final Credential item = (Credential) view.getTag();
92 if (item != null) {
93 CredentialDialogFragment.show(this, item);
94 }
Robin Lee04046a12016-01-19 11:42:57 +000095 }
Robin Leebaefdcf2015-08-26 10:57:44 +010096
Doris Ling03a3b512017-10-18 14:25:01 -070097 @Override
Doris Linged4685f2017-10-25 14:08:57 -070098 public void onCreate(@Nullable Bundle savedInstanceState) {
99 super.onCreate(savedInstanceState);
Doris Ling4a012832017-11-13 17:58:13 -0800100 getActivity().setTitle(R.string.user_credentials);
Doris Ling03a3b512017-10-18 14:25:01 -0700101 }
102
Robin Lee11fd5502016-05-16 15:42:34 +0100103 protected void announceRemoval(String alias) {
Robin Leeccaf9c92017-03-24 14:50:05 +0000104 if (!isAdded()) {
105 return;
Robin Lee11fd5502016-05-16 15:42:34 +0100106 }
Robin Leeccaf9c92017-03-24 14:50:05 +0000107 getListView().announceForAccessibility(getString(R.string.user_credential_removed, alias));
Robin Lee11fd5502016-05-16 15:42:34 +0100108 }
109
Robin Lee04046a12016-01-19 11:42:57 +0000110 protected void refreshItems() {
111 if (isAdded()) {
112 new AliasLoader().execute();
113 }
114 }
Robin Leebaefdcf2015-08-26 10:57:44 +0100115
Fan Zhang1e516282016-09-16 12:45:07 -0700116 public static class CredentialDialogFragment extends InstrumentedDialogFragment {
Robin Lee04046a12016-01-19 11:42:57 +0000117 private static final String TAG = "CredentialDialogFragment";
118 private static final String ARG_CREDENTIAL = "credential";
119
120 public static void show(Fragment target, Credential item) {
121 final Bundle args = new Bundle();
122 args.putParcelable(ARG_CREDENTIAL, item);
123
Robin Leef8e2dbf2016-04-07 13:17:24 +0100124 if (target.getFragmentManager().findFragmentByTag(TAG) == null) {
125 final DialogFragment frag = new CredentialDialogFragment();
126 frag.setTargetFragment(target, /* requestCode */ -1);
127 frag.setArguments(args);
128 frag.show(target.getFragmentManager(), TAG);
129 }
Robin Lee04046a12016-01-19 11:42:57 +0000130 }
131
132 @Override
133 public Dialog onCreateDialog(Bundle savedInstanceState) {
134 final Credential item = (Credential) getArguments().getParcelable(ARG_CREDENTIAL);
Robin Leee68d9572016-07-19 16:10:39 +0100135
Robin Lee04046a12016-01-19 11:42:57 +0000136 View root = getActivity().getLayoutInflater()
137 .inflate(R.layout.user_credential_dialog, null);
138 ViewGroup infoContainer = (ViewGroup) root.findViewById(R.id.credential_container);
Robin Leee68d9572016-07-19 16:10:39 +0100139 View contentView = getCredentialView(item, R.layout.user_credential, null,
140 infoContainer, /* expanded */ true);
141 infoContainer.addView(contentView);
Robin Leec421db72016-03-11 16:22:23 +0000142
143 AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
Robin Lee04046a12016-01-19 11:42:57 +0000144 .setView(root)
145 .setTitle(R.string.user_credential_title)
Robin Leec421db72016-03-11 16:22:23 +0000146 .setPositiveButton(R.string.done, null);
147
148 final String restriction = UserManager.DISALLOW_CONFIG_CREDENTIALS;
149 final int myUserId = UserHandle.myUserId();
Philip P. Moltmanne3f72112018-08-28 15:01:43 -0700150 if (!RestrictedLockUtilsInternal.hasBaseUserRestriction(getContext(), restriction,
151 myUserId)) {
Robin Leec421db72016-03-11 16:22:23 +0000152 DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
153 @Override public void onClick(DialogInterface dialog, int id) {
Philip P. Moltmanne3f72112018-08-28 15:01:43 -0700154 final EnforcedAdmin admin = RestrictedLockUtilsInternal
155 .checkIfRestrictionEnforced(getContext(), restriction, myUserId);
Robin Leec421db72016-03-11 16:22:23 +0000156 if (admin != null) {
157 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(),
158 admin);
159 } else {
Robin Leee68d9572016-07-19 16:10:39 +0100160 new RemoveCredentialsTask(getContext(), getTargetFragment())
161 .execute(item);
Robin Leec421db72016-03-11 16:22:23 +0000162 }
163 dialog.dismiss();
164 }
165 };
Hai Shalom23666052019-03-07 15:54:25 -0800166 // TODO: b/127865361
167 // a safe means of clearing wifi certificates. Configs refer to aliases
168 // directly so deleting certs will break dependent access points.
169 // However, Wi-Fi used to remove this certificate from storage if the network
170 // was removed, regardless if it is used in more than one network.
171 // It has been decided to allow removing certificates from this menu, as we
172 // assume that the user who manually adds certificates must have a way to
173 // manually remove them.
174 builder.setNegativeButton(R.string.trusted_credentials_remove_label, listener);
Robin Leec421db72016-03-11 16:22:23 +0000175 }
176 return builder.create();
Robin Lee04046a12016-01-19 11:42:57 +0000177 }
178
Fan Zhang1e516282016-09-16 12:45:07 -0700179 @Override
180 public int getMetricsCategory() {
Fan Zhang31b21002019-01-16 13:49:47 -0800181 return SettingsEnums.DIALOG_USER_CREDENTIAL;
Fan Zhang1e516282016-09-16 12:45:07 -0700182 }
183
Robin Leee68d9572016-07-19 16:10:39 +0100184 /**
185 * Deletes all certificates and keys under a given alias.
186 *
187 * If the {@link Credential} is for a system alias, all active grants to the alias will be
Hai Shalom23666052019-03-07 15:54:25 -0800188 * removed using {@link KeyChain}. If the {@link Credential} is for Wi-Fi alias, all
189 * credentials and keys will be removed using {@link KeyStore}.
Robin Leee68d9572016-07-19 16:10:39 +0100190 */
191 private class RemoveCredentialsTask extends AsyncTask<Credential, Void, Credential[]> {
192 private Context context;
Robin Leeda7bc512016-02-24 17:39:32 +0000193 private Fragment targetFragment;
194
Robin Leee68d9572016-07-19 16:10:39 +0100195 public RemoveCredentialsTask(Context context, Fragment targetFragment) {
196 this.context = context;
Robin Leeda7bc512016-02-24 17:39:32 +0000197 this.targetFragment = targetFragment;
Robin Lee04046a12016-01-19 11:42:57 +0000198 }
Robin Leeda7bc512016-02-24 17:39:32 +0000199
200 @Override
Robin Leee68d9572016-07-19 16:10:39 +0100201 protected Credential[] doInBackground(Credential... credentials) {
202 for (final Credential credential : credentials) {
203 if (credential.isSystem()) {
204 removeGrantsAndDelete(credential);
Hai Shalom23666052019-03-07 15:54:25 -0800205 } else {
206 deleteWifiCredential(credential);
Robin Leeda7bc512016-02-24 17:39:32 +0000207 }
Robin Leeda7bc512016-02-24 17:39:32 +0000208 }
Robin Leee68d9572016-07-19 16:10:39 +0100209 return credentials;
210 }
211
Hai Shalom23666052019-03-07 15:54:25 -0800212 private void deleteWifiCredential(final Credential credential) {
Janis Danisevskisa05bd652021-01-25 14:54:48 -0800213 try {
Janis Danisevskis75a91832021-03-10 09:30:46 -0800214 final KeyStore keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER);
215 keyStore.load(
216 new AndroidKeyStoreLoadStoreParameter(
217 KeyProperties.NAMESPACE_WIFI));
Janis Danisevskisa05bd652021-01-25 14:54:48 -0800218 keyStore.deleteEntry(credential.getAlias());
219 } catch (Exception e) {
220 throw new RuntimeException("Failed to delete keys from keystore.");
Hai Shalom23666052019-03-07 15:54:25 -0800221 }
222 }
223
Robin Leee68d9572016-07-19 16:10:39 +0100224 private void removeGrantsAndDelete(final Credential credential) {
225 final KeyChainConnection conn;
226 try {
227 conn = KeyChain.bind(getContext());
228 } catch (InterruptedException e) {
229 Log.w(TAG, "Connecting to KeyChain", e);
230 return;
231 }
232
233 try {
234 IKeyChainService keyChain = conn.getService();
235 keyChain.removeKeyPair(credential.alias);
236 } catch (RemoteException e) {
237 Log.w(TAG, "Removing credentials", e);
238 } finally {
239 conn.close();
240 }
Robin Leeda7bc512016-02-24 17:39:32 +0000241 }
242
243 @Override
Robin Leee68d9572016-07-19 16:10:39 +0100244 protected void onPostExecute(Credential... credentials) {
Robin Lee11fd5502016-05-16 15:42:34 +0100245 if (targetFragment instanceof UserCredentialsSettings && targetFragment.isAdded()) {
246 final UserCredentialsSettings target = (UserCredentialsSettings) targetFragment;
Robin Leee68d9572016-07-19 16:10:39 +0100247 for (final Credential credential : credentials) {
248 target.announceRemoval(credential.alias);
Robin Lee11fd5502016-05-16 15:42:34 +0100249 }
250 target.refreshItems();
Robin Leeda7bc512016-02-24 17:39:32 +0000251 }
252 }
Robin Lee04046a12016-01-19 11:42:57 +0000253 }
Robin Leebaefdcf2015-08-26 10:57:44 +0100254 }
255
256 /**
257 * Opens a background connection to KeyStore to list user credentials.
258 * The credentials are stored in a {@link CredentialAdapter} attached to the main
259 * {@link ListView} in the fragment.
260 */
Robin Leee68d9572016-07-19 16:10:39 +0100261 private class AliasLoader extends AsyncTask<Void, Void, List<Credential>> {
262 /**
263 * @return a list of credentials ordered:
264 * <ol>
265 * <li>first by purpose;</li>
266 * <li>then by alias.</li>
267 * </ol>
268 */
Robin Leebaefdcf2015-08-26 10:57:44 +0100269 @Override
Robin Leee68d9572016-07-19 16:10:39 +0100270 protected List<Credential> doInBackground(Void... params) {
Robin Leee68d9572016-07-19 16:10:39 +0100271 // Certificates can be installed into SYSTEM_UID or WIFI_UID through CertInstaller.
272 final int myUserId = UserHandle.myUserId();
273 final int systemUid = UserHandle.getUid(myUserId, Process.SYSTEM_UID);
274 final int wifiUid = UserHandle.getUid(myUserId, Process.WIFI_UID);
275
Janis Danisevskisa05bd652021-01-25 14:54:48 -0800276 try {
Janis Danisevskis75a91832021-03-10 09:30:46 -0800277 KeyStore processKeystore = KeyStore.getInstance(KEYSTORE_PROVIDER);
Janis Danisevskisa05bd652021-01-25 14:54:48 -0800278 processKeystore.load(null);
279 KeyStore wifiKeystore = null;
280 if (myUserId == 0) {
Janis Danisevskis75a91832021-03-10 09:30:46 -0800281 wifiKeystore = KeyStore.getInstance(KEYSTORE_PROVIDER);
282 wifiKeystore.load(new AndroidKeyStoreLoadStoreParameter(
283 KeyProperties.NAMESPACE_WIFI));
Janis Danisevskisa05bd652021-01-25 14:54:48 -0800284 }
Robin Leee68d9572016-07-19 16:10:39 +0100285
Janis Danisevskisa05bd652021-01-25 14:54:48 -0800286 List<Credential> credentials = new ArrayList<>();
287 credentials.addAll(getCredentialsForUid(processKeystore, systemUid).values());
288 if (wifiKeystore != null) {
289 credentials.addAll(getCredentialsForUid(wifiKeystore, wifiUid).values());
Janis Danisevskisb705e1a2017-04-19 16:23:02 -0700290 }
Janis Danisevskisa05bd652021-01-25 14:54:48 -0800291 return credentials;
292 } catch (Exception e) {
293 throw new RuntimeException("Failed to load credentials from Keystore.", e);
294 }
Janis Danisevskisb705e1a2017-04-19 16:23:02 -0700295 }
296
Robin Leee68d9572016-07-19 16:10:39 +0100297 private SortedMap<String, Credential> getCredentialsForUid(KeyStore keyStore, int uid) {
Janis Danisevskisa05bd652021-01-25 14:54:48 -0800298 try {
299 final SortedMap<String, Credential> aliasMap = new TreeMap<>();
300 boolean isSystem = UserHandle.getAppId(uid) == Process.SYSTEM_UID;
301 Enumeration<String> aliases = keyStore.aliases();
302 while (aliases.hasMoreElements()) {
303 String alias = aliases.nextElement();
304 Credential c = new Credential(alias, uid);
305 Key key = null;
306 try {
307 key = keyStore.getKey(alias, null);
308 } catch (NoSuchAlgorithmException | UnrecoverableKeyException e) {
309 Log.e(TAG, "Error tying to retrieve key: " + alias, e);
310 continue;
311 }
312 if (key != null) {
313 // So we have a key
314 if (key instanceof SecretKey) {
315 // We don't display any symmetric key entries.
316 continue;
317 }
318 if (isSystem) {
Janis Danisevskisb705e1a2017-04-19 16:23:02 -0700319 // Do not show work profile keys in user credentials
320 if (alias.startsWith(LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT) ||
321 alias.startsWith(LockPatternUtils.PROFILE_KEY_NAME_DECRYPT)) {
322 continue;
323 }
324 // Do not show synthetic password keys in user credential
Janis Danisevskisa05bd652021-01-25 14:54:48 -0800325 // We should never reach this point because the synthetic password key
326 // is symmetric.
Janis Danisevskisb705e1a2017-04-19 16:23:02 -0700327 if (alias.startsWith(LockPatternUtils.SYNTHETIC_PASSWORD_KEY_PREFIX)) {
328 continue;
329 }
330 }
Janis Danisevskisa05bd652021-01-25 14:54:48 -0800331 // At this point we have determined that we have an asymmetric key.
332 // so we have at least a USER_KEY and USER_CERTIFICATE.
333 c.storedTypes.add(Credential.Type.USER_KEY);
334
335 Certificate[] certs = keyStore.getCertificateChain(alias);
336 if (certs != null) {
337 c.storedTypes.add(Credential.Type.USER_CERTIFICATE);
338 if (certs.length > 1) {
339 c.storedTypes.add(Credential.Type.CA_CERTIFICATE);
Janis Danisevskisb705e1a2017-04-19 16:23:02 -0700340 }
Rubin Xu52221d82017-02-09 11:09:11 +0000341 }
Janis Danisevskisa05bd652021-01-25 14:54:48 -0800342 } else {
343 // So there is no key but we have an alias. This must mean that we have
344 // some certificate.
345 if (keyStore.isCertificateEntry(alias)) {
346 c.storedTypes.add(Credential.Type.CA_CERTIFICATE);
347 } else {
348 // This is a weired inconsistent case that should not exist.
349 // Pure trusted certificate entries should be stored in CA_CERTIFICATE,
350 // but if isCErtificateEntry returns null this means that only the
351 // USER_CERTIFICATE is populated which should never be the case without
352 // a private key. It can still be retrieved with
353 // keystore.getCertificate().
354 c.storedTypes.add(Credential.Type.USER_CERTIFICATE);
Rubin Xu52221d82017-02-09 11:09:11 +0000355 }
Ricky Wai95792742016-05-24 19:28:53 +0100356 }
Janis Danisevskisa05bd652021-01-25 14:54:48 -0800357 aliasMap.put(alias, c);
Robin Leebaefdcf2015-08-26 10:57:44 +0100358 }
Janis Danisevskisa05bd652021-01-25 14:54:48 -0800359 return aliasMap;
360 } catch (KeyStoreException e) {
361 throw new RuntimeException("Failed to load credential from Android Keystore.", e);
Robin Leebaefdcf2015-08-26 10:57:44 +0100362 }
Robin Leebaefdcf2015-08-26 10:57:44 +0100363 }
364
365 @Override
Robin Leee68d9572016-07-19 16:10:39 +0100366 protected void onPostExecute(List<Credential> credentials) {
Robin Leeccaf9c92017-03-24 14:50:05 +0000367 if (!isAdded()) {
368 return;
369 }
370
371 if (credentials == null || credentials.size() == 0) {
372 // Create a "no credentials installed" message for the empty case.
373 TextView emptyTextView = (TextView) getActivity().findViewById(android.R.id.empty);
374 emptyTextView.setText(R.string.user_credential_none_installed);
375 setEmptyView(emptyTextView);
376 } else {
377 setEmptyView(null);
378 }
379
380 getListView().setAdapter(
381 new CredentialAdapter(credentials, UserCredentialsSettings.this));
Robin Leebaefdcf2015-08-26 10:57:44 +0100382 }
383 }
384
385 /**
386 * Helper class to display {@link Credential}s in a list.
387 */
Robin Leeccaf9c92017-03-24 14:50:05 +0000388 private static class CredentialAdapter extends RecyclerView.Adapter<ViewHolder> {
Robin Leee68d9572016-07-19 16:10:39 +0100389 private static final int LAYOUT_RESOURCE = R.layout.user_credential_preference;
390
Robin Leeccaf9c92017-03-24 14:50:05 +0000391 private final List<Credential> mItems;
392 private final View.OnClickListener mListener;
393
394 public CredentialAdapter(List<Credential> items, @Nullable View.OnClickListener listener) {
395 mItems = items;
396 mListener = listener;
Robin Leebaefdcf2015-08-26 10:57:44 +0100397 }
398
399 @Override
Robin Leeccaf9c92017-03-24 14:50:05 +0000400 public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
401 final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
402 return new ViewHolder(inflater.inflate(LAYOUT_RESOURCE, parent, false));
403 }
404
405 @Override
406 public void onBindViewHolder(ViewHolder h, int position) {
407 getCredentialView(mItems.get(position), LAYOUT_RESOURCE, h.itemView, null, false);
408 h.itemView.setTag(mItems.get(position));
409 h.itemView.setOnClickListener(mListener);
410 }
411
412 @Override
413 public int getItemCount() {
414 return mItems.size();
415 }
416 }
417
418 private static class ViewHolder extends RecyclerView.ViewHolder {
419 public ViewHolder(View item) {
420 super(item);
Robin Leebaefdcf2015-08-26 10:57:44 +0100421 }
422 }
423
Robin Leee68d9572016-07-19 16:10:39 +0100424 /**
425 * Mapping from View IDs in {@link R} to the types of credentials they describe.
426 */
427 private static final SparseArray<Credential.Type> credentialViewTypes = new SparseArray<>();
428 static {
Janis Danisevskisb705e1a2017-04-19 16:23:02 -0700429 credentialViewTypes.put(R.id.contents_userkey, Credential.Type.USER_KEY);
Robin Leee68d9572016-07-19 16:10:39 +0100430 credentialViewTypes.put(R.id.contents_usercrt, Credential.Type.USER_CERTIFICATE);
431 credentialViewTypes.put(R.id.contents_cacrt, Credential.Type.CA_CERTIFICATE);
432 }
433
434 protected static View getCredentialView(Credential item, @LayoutRes int layoutResource,
435 @Nullable View view, ViewGroup parent, boolean expanded) {
436 if (view == null) {
437 view = LayoutInflater.from(parent.getContext()).inflate(layoutResource, parent, false);
438 }
439
440 ((TextView) view.findViewById(R.id.alias)).setText(item.alias);
441 ((TextView) view.findViewById(R.id.purpose)).setText(item.isSystem()
442 ? R.string.credential_for_vpn_and_apps
443 : R.string.credential_for_wifi);
444
445 view.findViewById(R.id.contents).setVisibility(expanded ? View.VISIBLE : View.GONE);
446 if (expanded) {
447 for (int i = 0; i < credentialViewTypes.size(); i++) {
448 final View detail = view.findViewById(credentialViewTypes.keyAt(i));
449 detail.setVisibility(item.storedTypes.contains(credentialViewTypes.valueAt(i))
450 ? View.VISIBLE : View.GONE);
451 }
452 }
453 return view;
454 }
455
456 static class AliasEntry {
457 public String alias;
458 public int uid;
459 }
460
Robin Leee2680422016-01-25 12:24:27 +0000461 static class Credential implements Parcelable {
462 static enum Type {
Robin Leebaefdcf2015-08-26 10:57:44 +0100463 CA_CERTIFICATE (Credentials.CA_CERTIFICATE),
464 USER_CERTIFICATE (Credentials.USER_CERTIFICATE),
Janis Danisevskisb705e1a2017-04-19 16:23:02 -0700465 USER_KEY(Credentials.USER_PRIVATE_KEY, Credentials.USER_SECRET_KEY);
Robin Leebaefdcf2015-08-26 10:57:44 +0100466
Janis Danisevskisb705e1a2017-04-19 16:23:02 -0700467 final String[] prefix;
Robin Leebaefdcf2015-08-26 10:57:44 +0100468
Janis Danisevskisb705e1a2017-04-19 16:23:02 -0700469 Type(String... prefix) {
Robin Leebaefdcf2015-08-26 10:57:44 +0100470 this.prefix = prefix;
471 }
472 }
473
474 /**
475 * Main part of the credential's alias. To fetch an item from KeyStore, prepend one of the
476 * prefixes from {@link CredentialItem.storedTypes}.
477 */
478 final String alias;
479
480 /**
Robin Leee68d9572016-07-19 16:10:39 +0100481 * UID under which this credential is stored. Typically {@link Process#SYSTEM_UID} but can
482 * also be {@link Process#WIFI_UID} for credentials installed as wifi certificates.
483 */
484 final int uid;
485
486 /**
Robin Leebaefdcf2015-08-26 10:57:44 +0100487 * Should contain some non-empty subset of:
488 * <ul>
489 * <li>{@link Credentials.CA_CERTIFICATE}</li>
490 * <li>{@link Credentials.USER_CERTIFICATE}</li>
Janis Danisevskisb705e1a2017-04-19 16:23:02 -0700491 * <li>{@link Credentials.USER_KEY}</li>
Robin Leebaefdcf2015-08-26 10:57:44 +0100492 * </ul>
493 */
Robin Lee04046a12016-01-19 11:42:57 +0000494 final EnumSet<Type> storedTypes = EnumSet.noneOf(Type.class);
Robin Leebaefdcf2015-08-26 10:57:44 +0100495
Robin Leee68d9572016-07-19 16:10:39 +0100496 Credential(final String alias, final int uid) {
Robin Leebaefdcf2015-08-26 10:57:44 +0100497 this.alias = alias;
Robin Leee68d9572016-07-19 16:10:39 +0100498 this.uid = uid;
Robin Leebaefdcf2015-08-26 10:57:44 +0100499 }
Robin Lee04046a12016-01-19 11:42:57 +0000500
501 Credential(Parcel in) {
Robin Leee68d9572016-07-19 16:10:39 +0100502 this(in.readString(), in.readInt());
Robin Lee04046a12016-01-19 11:42:57 +0000503
504 long typeBits = in.readLong();
505 for (Type i : Type.values()) {
506 if ((typeBits & (1L << i.ordinal())) != 0L) {
507 storedTypes.add(i);
508 }
509 }
510 }
511
512 public void writeToParcel(Parcel out, int flags) {
513 out.writeString(alias);
Robin Leee68d9572016-07-19 16:10:39 +0100514 out.writeInt(uid);
Robin Lee04046a12016-01-19 11:42:57 +0000515
516 long typeBits = 0;
517 for (Type i : storedTypes) {
518 typeBits |= 1L << i.ordinal();
519 }
520 out.writeLong(typeBits);
521 }
522
523 public int describeContents() {
524 return 0;
525 }
526
527 public static final Parcelable.Creator<Credential> CREATOR
528 = new Parcelable.Creator<Credential>() {
529 public Credential createFromParcel(Parcel in) {
530 return new Credential(in);
531 }
532
533 public Credential[] newArray(int size) {
534 return new Credential[size];
535 }
536 };
Robin Leee68d9572016-07-19 16:10:39 +0100537
538 public boolean isSystem() {
539 return UserHandle.getAppId(uid) == Process.SYSTEM_UID;
540 }
Hai Shalom23666052019-03-07 15:54:25 -0800541
542 public String getAlias() { return alias; }
543
544 public EnumSet<Type> getStoredTypes() {
545 return storedTypes;
546 }
Robin Leebaefdcf2015-08-26 10:57:44 +0100547 }
548}