• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 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.certinstaller;
18 
19 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
20 
21 import android.app.Activity;
22 import android.app.AlertDialog;
23 import android.app.Dialog;
24 import android.app.KeyguardManager;
25 import android.app.ProgressDialog;
26 import android.content.ActivityNotFoundException;
27 import android.content.Context;
28 import android.content.DialogInterface;
29 import android.content.Intent;
30 import android.os.AsyncTask;
31 import android.os.Bundle;
32 import android.os.Process;
33 import android.security.Credentials;
34 import android.security.KeyChain;
35 import android.security.KeyChain.KeyChainConnection;
36 import android.security.KeyStore;
37 import android.text.TextUtils;
38 import android.util.Log;
39 import android.view.View;
40 import android.view.ViewGroup;
41 import android.widget.AdapterView;
42 import android.widget.AdapterView.OnItemSelectedListener;
43 import android.widget.EditText;
44 import android.widget.Spinner;
45 import android.widget.Toast;
46 
47 import java.io.Serializable;
48 import java.security.cert.X509Certificate;
49 import java.util.LinkedHashMap;
50 import java.util.Map;
51 
52 /**
53  * Installs certificates to the system keystore.
54  */
55 public class CertInstaller extends Activity {
56     private static final String TAG = "CertInstaller";
57 
58     private static final int STATE_INIT = 1;
59     private static final int STATE_RUNNING = 2;
60     private static final int STATE_PAUSED = 3;
61 
62     private static final int NAME_CREDENTIAL_DIALOG = 1;
63     private static final int PKCS12_PASSWORD_DIALOG = 2;
64     private static final int PROGRESS_BAR_DIALOG = 3;
65 
66     private static final int REQUEST_SYSTEM_INSTALL_CODE = 1;
67     private static final int REQUEST_CONFIRM_CREDENTIALS = 2;
68 
69     // key to states Bundle
70     private static final String NEXT_ACTION_KEY = "na";
71 
72     // key to KeyStore
73     private static final String PKEY_MAP_KEY = "PKEY_MAP";
74 
75     // Values for usage type spinner
76     private static final int USAGE_TYPE_SYSTEM = 0;
77     private static final int USAGE_TYPE_WIFI = 1;
78 
79     private final KeyStore mKeyStore = KeyStore.getInstance();
80     private final ViewHelper mView = new ViewHelper();
81 
82     private int mState;
83     private CredentialHelper mCredentials;
84     private MyAction mNextAction;
85 
createCredentialHelper(Intent intent)86     private CredentialHelper createCredentialHelper(Intent intent) {
87         try {
88             return new CredentialHelper(intent);
89         } catch (Throwable t) {
90             Log.w(TAG, "createCredentialHelper", t);
91             toastErrorAndFinish(R.string.invalid_cert);
92             return new CredentialHelper();
93         }
94     }
95 
96     @Override
onCreate(Bundle savedStates)97     protected void onCreate(Bundle savedStates) {
98         super.onCreate(savedStates);
99         getWindow().addPrivateFlags(PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
100 
101         mCredentials = createCredentialHelper(getIntent());
102 
103         mState = (savedStates == null) ? STATE_INIT : STATE_RUNNING;
104 
105         if (mState == STATE_INIT) {
106             if (!mCredentials.containsAnyRawData()) {
107                 toastErrorAndFinish(R.string.no_cert_to_saved);
108                 finish();
109             } else {
110                 if (mCredentials.hasCaCerts()) {
111                     KeyguardManager keyguardManager = getSystemService(KeyguardManager.class);
112                     Intent intent = keyguardManager.createConfirmDeviceCredentialIntent(null, null);
113                     if (intent == null) { // No screenlock
114                         onScreenlockOk();
115                     } else {
116                         startActivityForResult(intent, REQUEST_CONFIRM_CREDENTIALS);
117                     }
118                 } else {
119                     onScreenlockOk();
120                 }
121             }
122         } else {
123             mCredentials.onRestoreStates(savedStates);
124             mNextAction = (MyAction)
125                     savedStates.getSerializable(NEXT_ACTION_KEY);
126         }
127     }
128 
129     @Override
onResume()130     protected void onResume() {
131         super.onResume();
132 
133         if (mState == STATE_INIT) {
134             mState = STATE_RUNNING;
135         } else {
136             if (mNextAction != null) {
137                 mNextAction.run(this);
138             }
139         }
140     }
141 
needsKeyStoreAccess()142     private boolean needsKeyStoreAccess() {
143         return ((mCredentials.hasKeyPair() || mCredentials.hasUserCertificate())
144                 && !mKeyStore.isUnlocked());
145     }
146 
147     @Override
onPause()148     protected void onPause() {
149         super.onPause();
150         mState = STATE_PAUSED;
151     }
152 
153     @Override
onSaveInstanceState(Bundle outStates)154     protected void onSaveInstanceState(Bundle outStates) {
155         super.onSaveInstanceState(outStates);
156         mCredentials.onSaveStates(outStates);
157         if (mNextAction != null) {
158             outStates.putSerializable(NEXT_ACTION_KEY, mNextAction);
159         }
160     }
161 
162     @Override
onCreateDialog(int dialogId)163     protected Dialog onCreateDialog (int dialogId) {
164         switch (dialogId) {
165             case PKCS12_PASSWORD_DIALOG:
166                 return createPkcs12PasswordDialog();
167 
168             case NAME_CREDENTIAL_DIALOG:
169                 return createNameCredentialDialog();
170 
171             case PROGRESS_BAR_DIALOG:
172                 ProgressDialog dialog = new ProgressDialog(this);
173                 dialog.setMessage(getString(R.string.extracting_pkcs12));
174                 dialog.setIndeterminate(true);
175                 dialog.setCancelable(false);
176                 return dialog;
177 
178             default:
179                 return null;
180         }
181     }
182 
183     @Override
onActivityResult(int requestCode, int resultCode, Intent data)184     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
185         if (requestCode == REQUEST_SYSTEM_INSTALL_CODE) {
186             if (resultCode == RESULT_OK) {
187                 Log.d(TAG, "credential is added: " + mCredentials.getName());
188                 Toast.makeText(this, getString(R.string.cert_is_added, mCredentials.getName()),
189                         Toast.LENGTH_LONG).show();
190 
191                 if (mCredentials.includesVpnAndAppsTrustAnchors()) {
192                     // more work to do, don't finish just yet
193                     new InstallVpnAndAppsTrustAnchorsTask().execute();
194                     return;
195                 }
196                 setResult(RESULT_OK);
197             } else {
198                 Log.d(TAG, "credential not saved, err: " + resultCode);
199                 toastErrorAndFinish(R.string.cert_not_saved);
200             }
201         } else if (requestCode == REQUEST_CONFIRM_CREDENTIALS) {
202             if (resultCode == RESULT_OK) {
203                 onScreenlockOk();
204                 return;
205             }
206             // Fail to confirm credentials. Let it finish
207         } else {
208             Log.w(TAG, "unknown request code: " + requestCode);
209         }
210         finish();
211     }
212 
onScreenlockOk()213     private void onScreenlockOk() {
214         if (mCredentials.hasPkcs12KeyStore()) {
215             if (mCredentials.hasPassword()) {
216                 showDialog(PKCS12_PASSWORD_DIALOG);
217             } else {
218                 new Pkcs12ExtractAction("").run(this);
219             }
220         } else {
221             MyAction action = new InstallOthersAction();
222             if (needsKeyStoreAccess()) {
223                 sendUnlockKeyStoreIntent();
224                 mNextAction = action;
225             } else {
226                 action.run(this);
227             }
228         }
229     }
230 
231     private class InstallVpnAndAppsTrustAnchorsTask extends AsyncTask<Void, Void, Boolean> {
232 
doInBackground(Void... unused)233         @Override protected Boolean doInBackground(Void... unused) {
234             try {
235                 KeyChainConnection keyChainConnection = KeyChain.bind(CertInstaller.this);
236                 try {
237                     return mCredentials.installVpnAndAppsTrustAnchors(CertInstaller.this,
238                             keyChainConnection.getService());
239                 } finally {
240                     keyChainConnection.close();
241                 }
242             } catch (InterruptedException e) {
243                 Thread.currentThread().interrupt();
244                 return false;
245             }
246         }
247 
onPostExecute(Boolean success)248         @Override protected void onPostExecute(Boolean success) {
249             if (success) {
250                 setResult(RESULT_OK);
251             }
252             finish();
253         }
254     }
255 
installOthers()256     void installOthers() {
257         if (mCredentials.hasKeyPair()) {
258             saveKeyPair();
259             finish();
260         } else {
261             X509Certificate cert = mCredentials.getUserCertificate();
262             if (cert != null) {
263                 // find matched private key
264                 String key = Util.toMd5(cert.getPublicKey().getEncoded());
265                 Map<String, byte[]> map = getPkeyMap();
266                 byte[] privatekey = map.get(key);
267                 if (privatekey != null) {
268                     Log.d(TAG, "found matched key: " + privatekey);
269                     map.remove(key);
270                     savePkeyMap(map);
271 
272                     mCredentials.setPrivateKey(privatekey);
273                 } else {
274                     Log.d(TAG, "didn't find matched private key: " + key);
275                 }
276             }
277             nameCredential();
278         }
279     }
280 
sendUnlockKeyStoreIntent()281     private void sendUnlockKeyStoreIntent() {
282         Credentials.getInstance().unlock(this);
283     }
284 
nameCredential()285     private void nameCredential() {
286         if (!mCredentials.hasAnyForSystemInstall()) {
287             toastErrorAndFinish(R.string.no_cert_to_saved);
288         } else {
289             showDialog(NAME_CREDENTIAL_DIALOG);
290         }
291     }
292 
saveKeyPair()293     private void saveKeyPair() {
294         byte[] privatekey = mCredentials.getData(Credentials.EXTRA_PRIVATE_KEY);
295         String key = Util.toMd5(mCredentials.getData(Credentials.EXTRA_PUBLIC_KEY));
296         Map<String, byte[]> map = getPkeyMap();
297         map.put(key, privatekey);
298         savePkeyMap(map);
299         Log.d(TAG, "save privatekey: " + key + " --> #keys:" + map.size());
300     }
301 
savePkeyMap(Map<String, byte[]> map)302     private void savePkeyMap(Map<String, byte[]> map) {
303         if (map.isEmpty()) {
304             if (!mKeyStore.delete(PKEY_MAP_KEY)) {
305                 Log.w(TAG, "savePkeyMap(): failed to delete pkey map");
306             }
307             return;
308         }
309         byte[] bytes = Util.toBytes(map);
310         if (!mKeyStore.put(PKEY_MAP_KEY, bytes, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)) {
311             Log.w(TAG, "savePkeyMap(): failed to write pkey map");
312         }
313     }
314 
getPkeyMap()315     private Map<String, byte[]> getPkeyMap() {
316         byte[] bytes = mKeyStore.get(PKEY_MAP_KEY);
317         if (bytes != null) {
318             Map<String, byte[]> map =
319                     (Map<String, byte[]>) Util.fromBytes(bytes);
320             if (map != null) return map;
321         }
322         return new MyMap();
323     }
324 
extractPkcs12InBackground(final String password)325     void extractPkcs12InBackground(final String password) {
326         // show progress bar and extract certs in a background thread
327         showDialog(PROGRESS_BAR_DIALOG);
328 
329         new AsyncTask<Void,Void,Boolean>() {
330             @Override protected Boolean doInBackground(Void... unused) {
331                 return mCredentials.extractPkcs12(password);
332             }
333             @Override protected void onPostExecute(Boolean success) {
334                 MyAction action = new OnExtractionDoneAction(success);
335                 if (mState == STATE_PAUSED) {
336                     // activity is paused; run it in next onResume()
337                     mNextAction = action;
338                 } else {
339                     action.run(CertInstaller.this);
340                 }
341             }
342         }.execute();
343     }
344 
onExtractionDone(boolean success)345     void onExtractionDone(boolean success) {
346         mNextAction = null;
347         removeDialog(PROGRESS_BAR_DIALOG);
348         if (success) {
349             removeDialog(PKCS12_PASSWORD_DIALOG);
350             nameCredential();
351         } else {
352             showDialog(PKCS12_PASSWORD_DIALOG);
353             mView.setText(R.id.credential_password, "");
354             mView.showError(R.string.password_error);
355         }
356     }
357 
createPkcs12PasswordDialog()358     private Dialog createPkcs12PasswordDialog() {
359         View view = View.inflate(this, R.layout.password_dialog, null);
360         mView.setView(view);
361         if (mView.getHasEmptyError()) {
362             mView.showError(R.string.password_empty_error);
363             mView.setHasEmptyError(false);
364         }
365 
366         String title = mCredentials.getName();
367         title = TextUtils.isEmpty(title)
368                 ? getString(R.string.pkcs12_password_dialog_title)
369                 : getString(R.string.pkcs12_file_password_dialog_title, title);
370         Dialog d = new AlertDialog.Builder(this)
371                 .setView(view)
372                 .setTitle(title)
373                 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
374                     public void onClick(DialogInterface dialog, int id) {
375                         String password = mView.getText(R.id.credential_password);
376                         mNextAction = new Pkcs12ExtractAction(password);
377                         mNextAction.run(CertInstaller.this);
378                      }
379                 })
380                 .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
381                     public void onClick(DialogInterface dialog, int id) {
382                         toastErrorAndFinish(R.string.cert_not_saved);
383                     }
384                 })
385                 .create();
386         d.setOnCancelListener(new DialogInterface.OnCancelListener() {
387             @Override public void onCancel(DialogInterface dialog) {
388                 toastErrorAndFinish(R.string.cert_not_saved);
389             }
390         });
391         return d;
392     }
393 
createNameCredentialDialog()394     private Dialog createNameCredentialDialog() {
395         ViewGroup view = (ViewGroup) View.inflate(this, R.layout.name_credential_dialog, null);
396         mView.setView(view);
397         if (mView.getHasEmptyError()) {
398             mView.showError(R.string.name_empty_error);
399             mView.setHasEmptyError(false);
400         }
401         mView.setText(R.id.credential_info, mCredentials.getDescription(this).toString());
402         final EditText nameInput = (EditText) view.findViewById(R.id.credential_name);
403         if (mCredentials.isInstallAsUidSet()) {
404             view.findViewById(R.id.credential_usage_group).setVisibility(View.GONE);
405         } else {
406             final Spinner usageSpinner = (Spinner) view.findViewById(R.id.credential_usage);
407 
408             usageSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
409                 @Override
410                 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
411                     switch ((int) id) {
412                         case USAGE_TYPE_SYSTEM:
413                             mCredentials.setInstallAsUid(KeyStore.UID_SELF);
414                             break;
415                         case USAGE_TYPE_WIFI:
416                             mCredentials.setInstallAsUid(Process.WIFI_UID);
417                             break;
418                         default:
419                             Log.w(TAG, "Unknown selection for scope: " + id);
420                     }
421                 }
422 
423                 @Override
424                 public void onNothingSelected(AdapterView<?> parent) {
425                 }
426             });
427         }
428         nameInput.setText(getDefaultName());
429         nameInput.selectAll();
430         final Context appContext = getApplicationContext();
431         Dialog d = new AlertDialog.Builder(this)
432                 .setView(view)
433                 .setTitle(R.string.name_credential_dialog_title)
434                 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
435                     public void onClick(DialogInterface dialog, int id) {
436                         String name = mView.getText(R.id.credential_name);
437                         if (TextUtils.isEmpty(name)) {
438                             mView.setHasEmptyError(true);
439                             removeDialog(NAME_CREDENTIAL_DIALOG);
440                             showDialog(NAME_CREDENTIAL_DIALOG);
441                         } else {
442                             removeDialog(NAME_CREDENTIAL_DIALOG);
443                             mCredentials.setName(name);
444 
445                             // install everything to system keystore
446                             try {
447                                 startActivityForResult(
448                                         mCredentials.createSystemInstallIntent(appContext),
449                                         REQUEST_SYSTEM_INSTALL_CODE);
450                             } catch (ActivityNotFoundException e) {
451                                 Log.w(TAG, "systemInstall(): " + e);
452                                 toastErrorAndFinish(R.string.cert_not_saved);
453                             }
454                         }
455                     }
456                 })
457                 .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
458                     public void onClick(DialogInterface dialog, int id) {
459                         toastErrorAndFinish(R.string.cert_not_saved);
460                     }
461                 })
462                 .create();
463         d.setOnCancelListener(new DialogInterface.OnCancelListener() {
464             @Override public void onCancel(DialogInterface dialog) {
465                 toastErrorAndFinish(R.string.cert_not_saved);
466             }
467         });
468         return d;
469     }
470 
getDefaultName()471     private String getDefaultName() {
472         String name = mCredentials.getName();
473         if (TextUtils.isEmpty(name)) {
474             return null;
475         } else {
476             // remove the extension from the file name
477             int index = name.lastIndexOf(".");
478             if (index > 0) name = name.substring(0, index);
479             return name;
480         }
481     }
482 
toastErrorAndFinish(int msgId)483     private void toastErrorAndFinish(int msgId) {
484         Toast.makeText(this, msgId, Toast.LENGTH_SHORT).show();
485         finish();
486     }
487 
488     private static class MyMap extends LinkedHashMap<String, byte[]>
489             implements Serializable {
490         private static final long serialVersionUID = 1L;
491 
492         @Override
removeEldestEntry(Map.Entry eldest)493         protected boolean removeEldestEntry(Map.Entry eldest) {
494             // Note: one key takes about 1300 bytes in the keystore, so be
495             // cautious about allowing more outstanding keys in the map that
496             // may go beyond keystore's max length for one entry.
497             return (size() > 3);
498         }
499     }
500 
501     private interface MyAction extends Serializable {
run(CertInstaller host)502         void run(CertInstaller host);
503     }
504 
505     private static class Pkcs12ExtractAction implements MyAction {
506         private final String mPassword;
507         private transient boolean hasRun;
508 
Pkcs12ExtractAction(String password)509         Pkcs12ExtractAction(String password) {
510             mPassword = password;
511         }
512 
run(CertInstaller host)513         public void run(CertInstaller host) {
514             if (hasRun) {
515                 return;
516             }
517             hasRun = true;
518             host.extractPkcs12InBackground(mPassword);
519         }
520     }
521 
522     private static class InstallOthersAction implements MyAction {
run(CertInstaller host)523         public void run(CertInstaller host) {
524             host.mNextAction = null;
525             host.installOthers();
526         }
527     }
528 
529     private static class OnExtractionDoneAction implements MyAction {
530         private final boolean mSuccess;
531 
OnExtractionDoneAction(boolean success)532         OnExtractionDoneAction(boolean success) {
533             mSuccess = success;
534         }
535 
run(CertInstaller host)536         public void run(CertInstaller host) {
537             host.onExtractionDone(mSuccess);
538         }
539     }
540 }
541