• 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 android.app.Activity;
20 import android.app.AlertDialog;
21 import android.app.Dialog;
22 import android.app.ProgressDialog;
23 import android.content.ActivityNotFoundException;
24 import android.content.DialogInterface;
25 import android.content.Intent;
26 import android.os.Bundle;
27 import android.security.Credentials;
28 import android.security.KeyStore;
29 import android.text.TextUtils;
30 import android.util.Log;
31 import android.view.View;
32 import android.widget.Toast;
33 
34 import java.io.Serializable;
35 import java.security.cert.X509Certificate;
36 import java.util.LinkedHashMap;
37 import java.util.Map;
38 
39 /**
40  * Installs certificates to the system keystore.
41  */
42 public class CertInstaller extends Activity
43         implements DialogInterface.OnClickListener {
44     private static final String TAG = "CertInstaller";
45 
46     private static final int STATE_INIT = 1;
47     private static final int STATE_RUNNING = 2;
48     private static final int STATE_PAUSED = 3;
49 
50     private static final int NAME_CREDENTIAL_DIALOG = 1;
51     private static final int PKCS12_PASSWORD_DIALOG = 2;
52     private static final int PROGRESS_BAR_DIALOG = 3;
53 
54     private static final int REQUEST_SYSTEM_INSTALL_CODE = 1;
55 
56     // key to states Bundle
57     private static final String NEXT_ACTION_KEY = "na";
58 
59     // key to KeyStore
60     private static final byte[] PKEY_MAP_KEY = "PKEY_MAP".getBytes();
61 
62     private KeyStore mKeyStore = KeyStore.getInstance();
63     private ViewHelper mView = new ViewHelper();
64     private int mButtonClicked;
65 
66     private int mState;
67     private CredentialHelper mCredentials;
68     private MyAction mNextAction;
69 
70     @Override
onCreate(Bundle savedStates)71     protected void onCreate(Bundle savedStates) {
72         super.onCreate(savedStates);
73 
74         mCredentials = new CredentialHelper(getIntent());
75 
76         mState = (savedStates == null) ? STATE_INIT : STATE_RUNNING;
77 
78         if (mState == STATE_INIT) {
79             if (!mCredentials.containsAnyRawData()) {
80                 toastErrorAndFinish(R.string.no_cert_to_saved);
81                 finish();
82             } else if (mCredentials.hasPkcs12KeyStore()) {
83                 showDialog(PKCS12_PASSWORD_DIALOG);
84             } else {
85                 MyAction action = new InstallOthersAction();
86                 if (needsKeyStoreAccess()) {
87                     sendUnlockKeyStoreIntent();
88                     mNextAction = action;
89                 } else {
90                     action.run(this);
91                 }
92             }
93         } else {
94             mCredentials.onRestoreStates(savedStates);
95             mNextAction = (MyAction)
96                     savedStates.getSerializable(NEXT_ACTION_KEY);
97         }
98     }
99 
100     @Override
onResume()101     protected void onResume() {
102         super.onResume();
103 
104         if (mState == STATE_INIT) {
105             mState = STATE_RUNNING;
106         } else {
107             if (mNextAction != null) mNextAction.run(this);
108         }
109     }
110 
needsKeyStoreAccess()111     private boolean needsKeyStoreAccess() {
112         return ((mCredentials.hasKeyPair() || mCredentials.hasUserCertificate())
113                 && (mKeyStore.test() != KeyStore.NO_ERROR));
114     }
115 
116     @Override
onPause()117     protected void onPause() {
118         super.onPause();
119         mState = STATE_PAUSED;
120     }
121 
122     @Override
onSaveInstanceState(Bundle outStates)123     protected void onSaveInstanceState(Bundle outStates) {
124         super.onSaveInstanceState(outStates);
125         mCredentials.onSaveStates(outStates);
126         if (mNextAction != null) {
127             outStates.putSerializable(NEXT_ACTION_KEY, mNextAction);
128         }
129     }
130 
131     @Override
onCreateDialog(int dialogId)132     protected Dialog onCreateDialog (int dialogId) {
133         switch (dialogId) {
134             case PKCS12_PASSWORD_DIALOG:
135                 return createPkcs12PasswordDialog();
136 
137             case NAME_CREDENTIAL_DIALOG:
138                 return createNameCredentialDialog();
139 
140             case PROGRESS_BAR_DIALOG:
141                 ProgressDialog dialog = new ProgressDialog(this);
142                 dialog.setMessage(getString(R.string.extracting_pkcs12));
143                 dialog.setIndeterminate(true);
144                 dialog.setCancelable(false);
145                 return dialog;
146 
147             default:
148                 return null;
149         }
150     }
151 
152     @Override
onPrepareDialog(int dialogId, Dialog dialog)153     protected void onPrepareDialog (int dialogId, Dialog dialog) {
154         super.onPrepareDialog(dialogId, dialog);
155         mButtonClicked = DialogInterface.BUTTON_NEGATIVE;
156     }
157 
158     @Override
onActivityResult(int requestCode, int resultCode, Intent data)159     protected void onActivityResult(int requestCode, int resultCode,
160             Intent data) {
161         if (requestCode == REQUEST_SYSTEM_INSTALL_CODE) {
162             if (resultCode == RESULT_OK) {
163                 Log.d(TAG, "credential is added: " + mCredentials.getName());
164                 Toast.makeText(this, getString(R.string.cert_is_added,
165                         mCredentials.getName()), Toast.LENGTH_LONG).show();
166                 setResult(RESULT_OK);
167             } else {
168                 Log.d(TAG, "credential not saved, err: " + resultCode);
169                 toastErrorAndFinish(R.string.cert_not_saved);
170             }
171         } else {
172             Log.w(TAG, "unknown request code: " + requestCode);
173         }
174         finish();
175     }
176 
installOthers()177     void installOthers() {
178         if (mCredentials.hasKeyPair()) {
179             saveKeyPair();
180             finish();
181         } else {
182             X509Certificate cert = mCredentials.getUserCertificate();
183             if (cert != null) {
184                 // find matched private key
185                 String key = Util.toMd5(cert.getPublicKey().getEncoded());
186                 Map<String, byte[]> map = getPkeyMap();
187                 byte[] privatekey = map.get(key);
188                 if (privatekey != null) {
189                     Log.d(TAG, "found matched key: " + privatekey);
190                     map.remove(key);
191                     savePkeyMap(map);
192 
193                     mCredentials.setPrivateKey(privatekey);
194                 } else {
195                     Log.d(TAG, "didn't find matched private key: " + key);
196                 }
197             }
198             nameCredential();
199         }
200     }
201 
sendUnlockKeyStoreIntent()202     private void sendUnlockKeyStoreIntent() {
203         Credentials.getInstance().unlock(this);
204     }
205 
nameCredential()206     private void nameCredential() {
207         if (!mCredentials.hasAnyForSystemInstall()) {
208             toastErrorAndFinish(R.string.no_cert_to_saved);
209         } else {
210             showDialog(NAME_CREDENTIAL_DIALOG);
211         }
212     }
213 
saveKeyPair()214     private void saveKeyPair() {
215         byte[] privatekey = mCredentials.getData(Credentials.PRIVATE_KEY);
216         String key = Util.toMd5(mCredentials.getData(Credentials.PUBLIC_KEY));
217         Map<String, byte[]> map = getPkeyMap();
218         map.put(key, privatekey);
219         savePkeyMap(map);
220         Log.d(TAG, "save privatekey: " + key + " --> #keys:" + map.size());
221     }
222 
savePkeyMap(Map<String, byte[]> map)223     private void savePkeyMap(Map<String, byte[]> map) {
224         byte[] bytes = Util.toBytes((Serializable) map);
225         if (!mKeyStore.put(PKEY_MAP_KEY, bytes)) {
226             Log.w(TAG, "savePkeyMap(): failed to write pkey map");
227         }
228     }
229 
getPkeyMap()230     private Map<String, byte[]> getPkeyMap() {
231         byte[] bytes = mKeyStore.get(PKEY_MAP_KEY);
232         if (bytes != null) {
233             Map<String, byte[]> map =
234                     (Map<String, byte[]>) Util.fromBytes(bytes);
235             if (map != null) return map;
236         }
237         return new MyMap();
238     }
239 
extractPkcs12InBackground(final String password)240     void extractPkcs12InBackground(final String password) {
241         // show progress bar and extract certs in a background thread
242         showDialog(PROGRESS_BAR_DIALOG);
243 
244         new Thread(new Runnable() {
245             public void run() {
246                 final boolean success = mCredentials.extractPkcs12(password);
247 
248                 runOnUiThread(new Runnable() {
249                     public void run() {
250                         MyAction action = new OnExtractionDoneAction(success);
251                         if (mState == STATE_PAUSED) {
252                             // activity is paused; run it in next onResume()
253                             mNextAction = action;
254                         } else {
255                             action.run(CertInstaller.this);
256                         }
257                     }
258                 });
259             }
260         }).start();
261     }
262 
onExtractionDone(boolean success)263     void onExtractionDone(boolean success) {
264         mNextAction = null;
265         removeDialog(PROGRESS_BAR_DIALOG);
266         if (success) {
267             removeDialog(PKCS12_PASSWORD_DIALOG);
268             nameCredential();
269         } else {
270             mView.setText(R.id.credential_password, "");
271             mView.showError(R.string.password_error);
272             showDialog(PKCS12_PASSWORD_DIALOG);
273         }
274     }
275 
onClick(DialogInterface dialog, int which)276     public void onClick(DialogInterface dialog, int which) {
277         mButtonClicked = which;
278     }
279 
createPkcs12PasswordDialog()280     private Dialog createPkcs12PasswordDialog() {
281         View view = View.inflate(this, R.layout.password_dialog, null);
282         mView.setView(view);
283 
284         DialogInterface.OnDismissListener onDismissHandler =
285                 new DialogInterface.OnDismissListener() {
286             public void onDismiss(DialogInterface dialog) {
287                 if (mButtonClicked == DialogInterface.BUTTON_NEGATIVE) {
288                     toastErrorAndFinish(R.string.cert_not_saved);
289                     return;
290                 }
291 
292                 final String password = mView.getText(R.id.credential_password);
293 
294                 if (TextUtils.isEmpty(password)) {
295                     mView.showError(R.string.password_empty_error);
296                     showDialog(PKCS12_PASSWORD_DIALOG);
297                 } else {
298                     mNextAction = new Pkcs12ExtractAction(password);
299                     mNextAction.run(CertInstaller.this);
300                 }
301             }
302         };
303 
304         String title = mCredentials.getName();
305         title = TextUtils.isEmpty(title)
306                 ? getString(R.string.pkcs12_password_dialog_title)
307                 : getString(R.string.pkcs12_file_password_dialog_title, title);
308         Dialog d = new AlertDialog.Builder(this)
309                 .setView(view)
310                 .setTitle(title)
311                 .setPositiveButton(android.R.string.ok, this)
312                 .setNegativeButton(android.R.string.cancel, this)
313                 .create();
314         d.setOnDismissListener(onDismissHandler);
315         return d;
316     }
317 
createNameCredentialDialog()318     private Dialog createNameCredentialDialog() {
319         View view = View.inflate(this, R.layout.name_credential_dialog, null);
320         mView.setView(view);
321 
322         mView.setText(R.id.credential_info,
323                 mCredentials.getDescription(this).toString());
324 
325         DialogInterface.OnDismissListener onDismissHandler =
326                 new DialogInterface.OnDismissListener() {
327             public void onDismiss(DialogInterface dialog) {
328                 if (mButtonClicked == DialogInterface.BUTTON_NEGATIVE) {
329                     toastErrorAndFinish(R.string.cert_not_saved);
330                     return;
331                 }
332 
333                 String name = mView.getText(R.id.credential_name);
334                 if (TextUtils.isEmpty(name)) {
335                     mView.showError(R.string.name_empty_error);
336                     showDialog(NAME_CREDENTIAL_DIALOG);
337                 } else {
338                     removeDialog(NAME_CREDENTIAL_DIALOG);
339                     mCredentials.setName(name);
340 
341                     // install everything to system keystore
342                     try {
343                         startActivityForResult(
344                                 mCredentials.createSystemInstallIntent(),
345                                 REQUEST_SYSTEM_INSTALL_CODE);
346                     } catch (ActivityNotFoundException e) {
347                         Log.w(TAG, "systemInstall(): " + e);
348                         toastErrorAndFinish(R.string.cert_not_saved);
349                     }
350                 }
351             }
352         };
353 
354         mView.setText(R.id.credential_name, getDefaultName());
355         Dialog d = new AlertDialog.Builder(this)
356                 .setView(view)
357                 .setTitle(R.string.name_credential_dialog_title)
358                 .setPositiveButton(android.R.string.ok, this)
359                 .setNegativeButton(android.R.string.cancel, this)
360                 .create();
361         d.setOnDismissListener(onDismissHandler);
362         return d;
363     }
364 
getDefaultName()365     private String getDefaultName() {
366         String name = mCredentials.getName();
367         if (TextUtils.isEmpty(name)) {
368             return null;
369         } else {
370             // remove the extension from the file name
371             int index = name.lastIndexOf(".");
372             if (index > 0) name = name.substring(0, index);
373             return name;
374         }
375     }
376 
toastErrorAndFinish(int msgId)377     private void toastErrorAndFinish(int msgId) {
378         Toast.makeText(this, msgId, Toast.LENGTH_SHORT).show();
379         finish();
380     }
381 
382     private static class MyMap extends LinkedHashMap<String, byte[]>
383             implements Serializable {
384         private static final long serialVersionUID = 1L;
385 
removeEldestEntry(Map.Entry eldest)386         protected boolean removeEldestEntry(Map.Entry eldest) {
387             // Note: one key takes about 1300 bytes in the keystore, so be
388             // cautious about allowing more outstanding keys in the map that
389             // may go beyond keystore's max length for one entry.
390             return (size() > 3);
391         }
392     }
393 
394     private interface MyAction extends Serializable {
run(CertInstaller host)395         void run(CertInstaller host);
396     }
397 
398     private static class Pkcs12ExtractAction implements MyAction {
399         private String mPassword;
400         private transient boolean hasRun;
401 
Pkcs12ExtractAction(String password)402         Pkcs12ExtractAction(String password) {
403             mPassword = password;
404         }
405 
run(CertInstaller host)406         public void run(CertInstaller host) {
407             if (hasRun) return;
408             hasRun = true;
409             host.extractPkcs12InBackground(mPassword);
410         }
411     }
412 
413     private static class InstallOthersAction implements MyAction {
run(CertInstaller host)414         public void run(CertInstaller host) {
415             host.mNextAction = null;
416             host.installOthers();
417         }
418     }
419 
420     private static class OnExtractionDoneAction implements MyAction {
421         private boolean mSuccess;
422 
OnExtractionDoneAction(boolean success)423         OnExtractionDoneAction(boolean success) {
424             mSuccess = success;
425         }
426 
run(CertInstaller host)427         public void run(CertInstaller host) {
428             host.onExtractionDone(mSuccess);
429         }
430     }
431 }
432