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.keychain;
18 
19 import android.annotation.NonNull;
20 import android.app.Activity;
21 import android.app.AlertDialog;
22 import android.app.PendingIntent;
23 import android.app.admin.IDevicePolicyManager;
24 import android.content.Context;
25 import android.content.DialogInterface;
26 import android.content.Intent;
27 import android.content.pm.PackageManager;
28 import android.content.res.Resources;
29 import android.net.Uri;
30 import android.os.AsyncTask;
31 import android.os.RemoteException;
32 import android.os.ServiceManager;
33 import android.security.Credentials;
34 import android.security.IKeyChainAliasCallback;
35 import android.security.KeyChain;
36 import android.security.KeyStore;
37 import android.util.Log;
38 import android.view.ContextThemeWrapper;
39 import android.view.LayoutInflater;
40 import android.view.View;
41 import android.view.ViewGroup;
42 import android.widget.AdapterView;
43 import android.widget.BaseAdapter;
44 import android.widget.ListView;
45 import android.widget.RadioButton;
46 import android.widget.TextView;
47 
48 import com.android.internal.annotations.VisibleForTesting;
49 import com.android.keychain.internal.KeyInfoProvider;
50 import com.android.org.bouncycastle.asn1.x509.X509Name;
51 
52 import java.io.ByteArrayInputStream;
53 import java.io.InputStream;
54 import java.io.IOException;
55 import java.security.cert.CertificateException;
56 import java.security.cert.CertificateFactory;
57 import java.security.cert.X509Certificate;
58 import java.util.ArrayList;
59 import java.util.Arrays;
60 import java.util.Collections;
61 import java.util.List;
62 import java.util.concurrent.ExecutionException;
63 import java.util.stream.Collectors;
64 
65 import javax.security.auth.x500.X500Principal;
66 
67 public class KeyChainActivity extends Activity {
68     private static final String TAG = "KeyChain";
69 
70     private int mSenderUid;
71 
72     private PendingIntent mSender;
73 
74     // beware that some of these KeyStore operations such as saw and
75     // get do file I/O in the remote keystore process and while they
76     // do not cause StrictMode violations, they logically should not
77     // be done on the UI thread.
78     private KeyStore mKeyStore = KeyStore.getInstance();
79 
80     // A dialog to show the user while the KeyChain Activity is loading the
81     // certificates.
82     AlertDialog mLoadingDialog;
83 
onResume()84     @Override public void onResume() {
85         super.onResume();
86 
87         mSender = getIntent().getParcelableExtra(KeyChain.EXTRA_SENDER);
88         if (mSender == null) {
89             // if no sender, bail, we need to identify the app to the user securely.
90             finish(null);
91             return;
92         }
93         try {
94             mSenderUid = getPackageManager().getPackageInfo(
95                     mSender.getIntentSender().getTargetPackage(), 0).applicationInfo.uid;
96         } catch (PackageManager.NameNotFoundException e) {
97             // if unable to find the sender package info bail,
98             // we need to identify the app to the user securely.
99             finish(null);
100             return;
101         }
102 
103         chooseCertificate();
104     }
105 
showLoadingDialog()106     private void showLoadingDialog() {
107         final Context themedContext = new ContextThemeWrapper(
108                 this, com.android.internal.R.style.Theme_Translucent_NoTitleBar);
109         mLoadingDialog = new AlertDialog.Builder(themedContext)
110                 .setTitle(R.string.app_name)
111                 .setMessage(R.string.loading_certs_message)
112                 .create();
113         mLoadingDialog.show();
114     }
115 
chooseCertificate()116     private void chooseCertificate() {
117         // Start loading the set of certs to choose from now- if device policy doesn't return an
118         // alias, having aliases loading already will save some time waiting for UI to start.
119         KeyInfoProvider keyInfoProvider = new KeyInfoProvider() {
120             public boolean isUserSelectable(String alias) {
121                 try (KeyChain.KeyChainConnection connection =
122                         KeyChain.bind(KeyChainActivity.this)) {
123                     return connection.getService().isUserSelectable(alias);
124                 }
125                 catch (InterruptedException ignored) {
126                     Log.e(TAG, "interrupted while checking if key is user-selectable", ignored);
127                     Thread.currentThread().interrupt();
128                     return false;
129                 } catch (Exception ignored) {
130                     Log.e(TAG, "error while checking if key is user-selectable", ignored);
131                     return false;
132                 }
133             }
134         };
135 
136         Log.i(TAG, String.format("Requested by app uid %d to provide a private key alias",
137                 mSenderUid));
138 
139         String[] keyTypes = getIntent().getStringArrayExtra(KeyChain.EXTRA_KEY_TYPES);
140         if (keyTypes == null) {
141             keyTypes = new String[]{};
142         }
143         Log.i(TAG, String.format("Key types specified: %s", Arrays.toString(keyTypes)));
144 
145         ArrayList<byte[]> issuers = (ArrayList<byte[]>) getIntent().getSerializableExtra(
146                 KeyChain.EXTRA_ISSUERS);
147         if (issuers == null) {
148             issuers = new ArrayList<byte[]>();
149         } else {
150             Log.i(TAG, "Issuers specified, will be listed later.");
151         }
152 
153         final AliasLoader loader = new AliasLoader(mKeyStore, this, keyInfoProvider,
154                 new CertificateParametersFilter(mKeyStore, keyTypes, issuers));
155         loader.execute();
156 
157         final IKeyChainAliasCallback.Stub callback = new IKeyChainAliasCallback.Stub() {
158             @Override public void alias(String alias) {
159                 Log.i(TAG, String.format("Alias provided by device policy client: %s", alias));
160                 // Use policy-suggested alias if provided or abort further actions if alias is
161                 // KeyChain.KEY_ALIAS_SELECTION_DENIED
162                 if (alias != null) {
163                     finishWithAliasFromPolicy(alias);
164                     return;
165                 }
166 
167                 // No suggested alias - instead finish loading and show UI to pick one
168                 final CertificateAdapter certAdapter;
169                 try {
170                     certAdapter = loader.get();
171                 } catch (InterruptedException | ExecutionException e) {
172                     Log.e(TAG, "Loading certificate aliases interrupted", e);
173                     finish(null);
174                     return;
175                 }
176                 /*
177                  * If there are no keys for the user to choose from, do not display
178                  * the dialog. This is in line with what other operating systems do.
179                  */
180                 if (!certAdapter.hasKeysToChoose()) {
181                     Log.i(TAG, "No keys to choose from");
182                     finish(null);
183                     return;
184                 }
185                 runOnUiThread(new Runnable() {
186                     @Override public void run() {
187                         if (mLoadingDialog != null) {
188                             mLoadingDialog.dismiss();
189                             mLoadingDialog = null;
190                         }
191                         displayCertChooserDialog(certAdapter);
192                     }
193                 });
194             }
195         };
196 
197         // Show a dialog to the user to indicate long-running task.
198         showLoadingDialog();
199         // Give a profile or device owner the chance to intercept the request, if a private key
200         // access listener is registered with the DevicePolicyManagerService.
201         IDevicePolicyManager devicePolicyManager = IDevicePolicyManager.Stub.asInterface(
202                 ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
203 
204         Uri uri = getIntent().getParcelableExtra(KeyChain.EXTRA_URI);
205         String alias = getIntent().getStringExtra(KeyChain.EXTRA_ALIAS);
206         try {
207             devicePolicyManager.choosePrivateKeyAlias(mSenderUid, uri, alias, callback);
208         } catch (RemoteException e) {
209             Log.e(TAG, "Unable to request alias from DevicePolicyManager", e);
210             // Proceed without a suggested alias.
211             try {
212                 callback.alias(null);
213             } catch (RemoteException shouldNeverHappen) {
214                 finish(null);
215             }
216         }
217     }
218 
219     @VisibleForTesting
220     public static class CertificateParametersFilter {
221         private final KeyStore mKeyStore;
222         private final List<String> mKeyTypes;
223         private final List<X500Principal> mIssuers;
224 
CertificateParametersFilter(KeyStore keyStore, @NonNull String[] keyTypes, @NonNull ArrayList<byte[]> issuers)225         public CertificateParametersFilter(KeyStore keyStore,
226                 @NonNull String[] keyTypes, @NonNull ArrayList<byte[]> issuers) {
227             mKeyStore = keyStore;
228             mKeyTypes = Arrays.asList(keyTypes);
229             mIssuers = new ArrayList<X500Principal>();
230             for (byte[] issuer : issuers) {
231                 try {
232                     X500Principal issuerPrincipal = new X500Principal(issuer);
233                     Log.i(TAG, "Added issuer: " + issuerPrincipal.getName());
234                     mIssuers.add(new X500Principal(issuer));
235                 } catch (IllegalArgumentException e) {
236                     Log.w(TAG, "Skipping invalid issuer", e);
237                 }
238             }
239         }
240 
shouldPresentCertificate(String alias)241         public boolean shouldPresentCertificate(String alias) {
242             X509Certificate cert = loadCertificate(mKeyStore, alias);
243             // If there's no certificate associated with the alias, skip.
244             if (cert == null) {
245                 Log.i(TAG, String.format("No certificate associated with alias %s", alias));
246                 return false;
247             }
248             List<X509Certificate> certChain = new ArrayList(loadCertificateChain(mKeyStore, alias));
249             Log.i(TAG, String.format("Inspecting certificate %s aliased with %s, chain length %d",
250                         cert.getSubjectDN().getName(), alias, certChain.size()));
251 
252             // If the caller has provided a list of key types to restrict the certificates
253             // offered for selection, skip this alias if the key algorithm is not in that
254             // list.
255             // Note that the end entity (leaf) certificate's public key has to be compatible
256             // with the specified key algorithm, not any one of the chain (see RFC5246
257             // section 7.4.6)
258             String keyAlgorithm = cert.getPublicKey().getAlgorithm();
259             Log.i(TAG, String.format("Certificate key algorithm: %s", keyAlgorithm));
260             if (!mKeyTypes.isEmpty() && !mKeyTypes.contains(keyAlgorithm)) {
261                 return false;
262             }
263 
264             // If the caller has provided a list of issuers to restrict the certificates
265             // offered for selection, skip this alias if none of the issuers in the client
266             // certificate chain is in that list.
267             List<X500Principal> chainIssuers = new ArrayList();
268             chainIssuers.add(cert.getIssuerX500Principal());
269             for (X509Certificate intermediate : certChain) {
270                 X500Principal subject = intermediate.getSubjectX500Principal();
271                 Log.i(TAG, String.format("Subject of intermediate in client certificate chain: %s",
272                             subject.getName()));
273                 // Collect the subjects of all the intermediates, as the RFC specifies that
274                 // "one of the certificates in the certificate chain SHOULD be issued by one of
275                 // the listed CAs."
276                 chainIssuers.add(subject);
277             }
278 
279             if (!mIssuers.isEmpty()) {
280                 for (X500Principal issuer : chainIssuers) {
281                     if (mIssuers.contains(issuer)) {
282                         Log.i(TAG, String.format("Requested issuer found: %s", issuer));
283                         return true;
284                     }
285                 }
286                 return false;
287             }
288 
289             return true;
290         }
291     }
292 
293     @VisibleForTesting
294     static class AliasLoader extends AsyncTask<Void, Void, CertificateAdapter> {
295         private final KeyStore mKeyStore;
296         private final Context mContext;
297         private final KeyInfoProvider mInfoProvider;
298         private final CertificateParametersFilter mCertificateFilter;
299 
AliasLoader(KeyStore keyStore, Context context, KeyInfoProvider infoProvider, CertificateParametersFilter certificateFilter)300         public AliasLoader(KeyStore keyStore, Context context, KeyInfoProvider infoProvider,
301                 CertificateParametersFilter certificateFilter) {
302           mKeyStore = keyStore;
303           mContext = context;
304           mInfoProvider = infoProvider;
305           mCertificateFilter = certificateFilter;
306         }
307 
doInBackground(Void... params)308         @Override protected CertificateAdapter doInBackground(Void... params) {
309             String[] aliasArray = mKeyStore.list(Credentials.USER_PRIVATE_KEY);
310             List<String> rawAliasList = ((aliasArray == null)
311                                       ? Collections.<String>emptyList()
312                                       : Arrays.asList(aliasArray));
313 
314             return new CertificateAdapter(mKeyStore, mContext,
315                     rawAliasList.stream().filter(mInfoProvider::isUserSelectable)
316                     .filter(mCertificateFilter::shouldPresentCertificate)
317                     .sorted().collect(Collectors.toList()));
318         }
319     }
320 
displayCertChooserDialog(final CertificateAdapter adapter)321     private void displayCertChooserDialog(final CertificateAdapter adapter) {
322         if (adapter.mAliases.isEmpty()) {
323             Log.w(TAG, "Should not be asked to display the cert chooser without aliases.");
324             finish(null);
325             return;
326         }
327 
328         AlertDialog.Builder builder = new AlertDialog.Builder(this);
329         builder.setNegativeButton(R.string.deny_button, new DialogInterface.OnClickListener() {
330             @Override public void onClick(DialogInterface dialog, int id) {
331                 dialog.cancel(); // will cause OnDismissListener to be called
332             }
333         });
334 
335         int selectedItem = -1;
336         Resources res = getResources();
337         String alias = getIntent().getStringExtra(KeyChain.EXTRA_ALIAS);
338 
339         if (alias != null) {
340             // if alias was requested, set it if found
341             int adapterPosition = adapter.mAliases.indexOf(alias);
342             if (adapterPosition != -1) {
343                 // increase by 1 to account for item 0 being the header.
344                 selectedItem = adapterPosition + 1;
345             }
346         } else if (adapter.mAliases.size() == 1) {
347             // if only one choice, preselect it
348             selectedItem = 1;
349         }
350 
351         builder.setPositiveButton(R.string.allow_button, new DialogInterface.OnClickListener() {
352             @Override public void onClick(DialogInterface dialog, int id) {
353                 if (dialog instanceof AlertDialog) {
354                     ListView lv = ((AlertDialog) dialog).getListView();
355                     int listViewPosition = lv.getCheckedItemPosition();
356                     int adapterPosition = listViewPosition-1;
357                     String alias = ((adapterPosition >= 0)
358                                     ? adapter.getItem(adapterPosition)
359                                     : null);
360                     Log.i(TAG, String.format("User chose: %s", alias));
361                     finish(alias);
362                 } else {
363                     Log.wtf(TAG, "Expected AlertDialog, got " + dialog, new Exception());
364                     finish(null);
365                 }
366             }
367         });
368 
369         builder.setTitle(res.getString(R.string.title_select_cert));
370         builder.setSingleChoiceItems(adapter, selectedItem, null);
371         final AlertDialog dialog = builder.create();
372 
373         // Show text above the list to explain what the certificate will be used for.
374         TextView contextView = (TextView) View.inflate(
375                 this, R.layout.cert_chooser_header, null);
376 
377         final ListView lv = dialog.getListView();
378         lv.addHeaderView(contextView, null, false);
379         lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
380             public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
381                 if (position == 0) {
382                     // Header. Just text; ignore clicks.
383                     return;
384                 } else {
385                     dialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true);
386                     lv.setItemChecked(position, true);
387                     adapter.notifyDataSetChanged();
388                 }
389             }
390         });
391 
392         // getTargetPackage guarantees that the returned string is
393         // supplied by the system, so that an application can not
394         // spoof its package.
395         String pkg = mSender.getIntentSender().getTargetPackage();
396         PackageManager pm = getPackageManager();
397         CharSequence applicationLabel;
398         try {
399             applicationLabel = pm.getApplicationLabel(pm.getApplicationInfo(pkg, 0)).toString();
400         } catch (PackageManager.NameNotFoundException e) {
401             applicationLabel = pkg;
402         }
403         String appMessage = String.format(res.getString(R.string.requesting_application),
404                                           applicationLabel);
405         String contextMessage = appMessage;
406         Uri uri = getIntent().getParcelableExtra(KeyChain.EXTRA_URI);
407         if (uri != null) {
408             String hostMessage = String.format(res.getString(R.string.requesting_server),
409                                                uri.getAuthority());
410             if (contextMessage == null) {
411                 contextMessage = hostMessage;
412             } else {
413                 contextMessage += " " + hostMessage;
414             }
415         }
416         contextView.setText(contextMessage);
417 
418         if (selectedItem == -1) {
419             dialog.setOnShowListener(new DialogInterface.OnShowListener() {
420                 @Override
421                 public void onShow(DialogInterface dialogInterface) {
422                      dialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false);
423                 }
424             });
425         }
426         dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
427             @Override public void onCancel(DialogInterface dialog) {
428                 finish(null);
429             }
430         });
431         dialog.show();
432     }
433 
434     @VisibleForTesting
435     static class CertificateAdapter extends BaseAdapter {
436         private final List<String> mAliases;
437         private final List<String> mSubjects = new ArrayList<String>();
438         private final KeyStore mKeyStore;
439         private final Context mContext;
440 
CertificateAdapter(KeyStore keyStore, Context context, List<String> aliases)441         private CertificateAdapter(KeyStore keyStore, Context context, List<String> aliases) {
442             mAliases = aliases;
443             mSubjects.addAll(Collections.nCopies(aliases.size(), (String) null));
444             mKeyStore = keyStore;
445             mContext = context;
446         }
getCount()447         @Override public int getCount() {
448             return mAliases.size();
449         }
getItem(int adapterPosition)450         @Override public String getItem(int adapterPosition) {
451             return mAliases.get(adapterPosition);
452         }
getItemId(int adapterPosition)453         @Override public long getItemId(int adapterPosition) {
454             return adapterPosition;
455         }
getView(final int adapterPosition, View view, ViewGroup parent)456         @Override public View getView(final int adapterPosition, View view, ViewGroup parent) {
457             ViewHolder holder;
458             if (view == null) {
459                 LayoutInflater inflater = LayoutInflater.from(mContext);
460                 view = inflater.inflate(R.layout.cert_item, parent, false);
461                 holder = new ViewHolder();
462                 holder.mAliasTextView = (TextView) view.findViewById(R.id.cert_item_alias);
463                 holder.mSubjectTextView = (TextView) view.findViewById(R.id.cert_item_subject);
464                 holder.mRadioButton = (RadioButton) view.findViewById(R.id.cert_item_selected);
465                 view.setTag(holder);
466             } else {
467                 holder = (ViewHolder) view.getTag();
468             }
469 
470             String alias = mAliases.get(adapterPosition);
471 
472             holder.mAliasTextView.setText(alias);
473 
474             String subject = mSubjects.get(adapterPosition);
475             if (subject == null) {
476                 new CertLoader(adapterPosition, holder.mSubjectTextView).execute();
477             } else {
478                 holder.mSubjectTextView.setText(subject);
479             }
480 
481             ListView lv = (ListView)parent;
482             int listViewCheckedItemPosition = lv.getCheckedItemPosition();
483             int adapterCheckedItemPosition = listViewCheckedItemPosition-1;
484             holder.mRadioButton.setChecked(adapterPosition == adapterCheckedItemPosition);
485             return view;
486         }
487 
488         /**
489          * Returns true if there are keys to choose from.
490          */
hasKeysToChoose()491         public boolean hasKeysToChoose() {
492             return !mAliases.isEmpty();
493         }
494 
495         private class CertLoader extends AsyncTask<Void, Void, String> {
496             private final int mAdapterPosition;
497             private final TextView mSubjectView;
CertLoader(int adapterPosition, TextView subjectView)498             private CertLoader(int adapterPosition, TextView subjectView) {
499                 mAdapterPosition = adapterPosition;
500                 mSubjectView = subjectView;
501             }
doInBackground(Void... params)502             @Override protected String doInBackground(Void... params) {
503                 String alias = mAliases.get(mAdapterPosition);
504                 X509Certificate cert = loadCertificate(mKeyStore, alias);
505                 if (cert == null) {
506                     return null;
507                 }
508                 // bouncycastle can handle the emailAddress OID of 1.2.840.113549.1.9.1
509                 X500Principal subjectPrincipal = cert.getSubjectX500Principal();
510                 X509Name subjectName = X509Name.getInstance(subjectPrincipal.getEncoded());
511                 return subjectName.toString(true, X509Name.DefaultSymbols);
512             }
onPostExecute(String subjectString)513             @Override protected void onPostExecute(String subjectString) {
514                 mSubjects.set(mAdapterPosition, subjectString);
515                 mSubjectView.setText(subjectString);
516             }
517         }
518     }
519 
520     private static class ViewHolder {
521         TextView mAliasTextView;
522         TextView mSubjectTextView;
523         RadioButton mRadioButton;
524     }
525 
finish(String alias)526     private void finish(String alias) {
527         finish(alias, false);
528     }
529 
finishWithAliasFromPolicy(String alias)530     private void finishWithAliasFromPolicy(String alias) {
531         finish(alias, true);
532     }
533 
finish(String alias, boolean isAliasFromPolicy)534     private void finish(String alias, boolean isAliasFromPolicy) {
535         if (mLoadingDialog != null) {
536             mLoadingDialog.dismiss();
537             mLoadingDialog = null;
538         }
539         if (alias == null || alias.equals(KeyChain.KEY_ALIAS_SELECTION_DENIED)) {
540             alias = null;
541             setResult(RESULT_CANCELED);
542         } else {
543             Intent result = new Intent();
544             result.putExtra(Intent.EXTRA_TEXT, alias);
545             setResult(RESULT_OK, result);
546         }
547         IKeyChainAliasCallback keyChainAliasResponse
548                 = IKeyChainAliasCallback.Stub.asInterface(
549                         getIntent().getIBinderExtra(KeyChain.EXTRA_RESPONSE));
550         if (keyChainAliasResponse != null) {
551             new ResponseSender(keyChainAliasResponse, alias, isAliasFromPolicy).execute();
552             return;
553         }
554         finish();
555     }
556 
557     private class ResponseSender extends AsyncTask<Void, Void, Void> {
558         private IKeyChainAliasCallback mKeyChainAliasResponse;
559         private String mAlias;
560         private boolean mFromPolicy;
561 
ResponseSender(IKeyChainAliasCallback keyChainAliasResponse, String alias, boolean isFromPolicy)562         private ResponseSender(IKeyChainAliasCallback keyChainAliasResponse, String alias,
563                 boolean isFromPolicy) {
564             mKeyChainAliasResponse = keyChainAliasResponse;
565             mAlias = alias;
566             mFromPolicy = isFromPolicy;
567         }
doInBackground(Void... unused)568         @Override protected Void doInBackground(Void... unused) {
569             try {
570                 if (mAlias != null) {
571                     KeyChain.KeyChainConnection connection = KeyChain.bind(KeyChainActivity.this);
572                     try {
573                         // This is a safety check to make sure an alias was not somehow chosen by
574                         // the user but is not user-selectable.
575                         // However, if the alias was selected by the Device Owner / Profile Owner
576                         // (by implementing DeviceAdminReceiver), then there's no need to check
577                         // this.
578                         if (!mFromPolicy && (!connection.getService().isUserSelectable(mAlias))) {
579                             Log.w(TAG, String.format("Alias %s not user-selectable.", mAlias));
580                             //TODO: Should we invoke the callback with null here to indicate error?
581                             return null;
582                         }
583                         connection.getService().setGrant(mSenderUid, mAlias, true);
584                     } finally {
585                         connection.close();
586                     }
587                 }
588                 mKeyChainAliasResponse.alias(mAlias);
589             } catch (InterruptedException ignored) {
590                 Thread.currentThread().interrupt();
591                 Log.d(TAG, "interrupted while granting access", ignored);
592             } catch (Exception ignored) {
593                 // don't just catch RemoteException, caller could
594                 // throw back a RuntimeException across processes
595                 // which we should protect against.
596                 Log.e(TAG, "error while granting access", ignored);
597             }
598             return null;
599         }
onPostExecute(Void unused)600         @Override protected void onPostExecute(Void unused) {
601             finish();
602         }
603     }
604 
onBackPressed()605     @Override public void onBackPressed() {
606         finish(null);
607     }
608 
loadCertificate(KeyStore keyStore, String alias)609     private static X509Certificate loadCertificate(KeyStore keyStore, String alias) {
610         byte[] bytes = keyStore.get(Credentials.USER_CERTIFICATE + alias);
611         if (bytes == null) {
612             Log.i(TAG, String.format("Missing user certificate for key alias %s", alias));
613             return null;
614         }
615         InputStream in = new ByteArrayInputStream(bytes);
616         try {
617             CertificateFactory cf = CertificateFactory.getInstance("X.509");
618             return (X509Certificate)cf.generateCertificate(in);
619         } catch (CertificateException ignored) {
620             Log.w(TAG, "Error generating certificate", ignored);
621             return null;
622         }
623     }
624 
loadCertificateChain(KeyStore keyStore, String alias)625     private static List<X509Certificate> loadCertificateChain(KeyStore keyStore, String alias) {
626         byte[] chainBytes = keyStore.get(Credentials.CA_CERTIFICATE + alias);
627         if (chainBytes == null) {
628             Log.i(TAG, String.format("Missing certificate chain for key alias %s", alias));
629             return Collections.emptyList();
630         }
631 
632         try {
633             return Credentials.convertFromPem(chainBytes);
634         } catch (IOException | CertificateException e) {
635             Log.w(TAG, String.format("Error parsing certificate chain for alias %s", alias), e);
636             return Collections.emptyList();
637         }
638     }
639 }
640