summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorHung-ying Tyan <tyanh@google.com>2009-09-28 10:26:15 +0800
committerHung-ying Tyan <tyanh@google.com>2009-09-28 18:06:30 +0800
commitc87a48ac1c22403b690330f0cf7a1890f9a0c4eb (patch)
tree613d3c6dd5c4683c25b872951099b6c6f0032361 /src
parentbedff945a7dffd019035154f78018b350e47ee66 (diff)
downloadandroid_packages_apps_CertInstaller-c87a48ac1c22403b690330f0cf7a1890f9a0c4eb.tar.gz
android_packages_apps_CertInstaller-c87a48ac1c22403b690330f0cf7a1890f9a0c4eb.tar.bz2
android_packages_apps_CertInstaller-c87a48ac1c22403b690330f0cf7a1890f9a0c4eb.zip
Fix the issue where cert comes later than keypair.
Diffstat (limited to 'src')
-rw-r--r--src/com/android/certinstaller/CertInstaller.java849
1 files changed, 841 insertions, 8 deletions
diff --git a/src/com/android/certinstaller/CertInstaller.java b/src/com/android/certinstaller/CertInstaller.java
index fbc82ff..ca1d252 100644
--- a/src/com/android/certinstaller/CertInstaller.java
+++ b/src/com/android/certinstaller/CertInstaller.java
@@ -19,10 +19,18 @@ package com.android.certinstaller;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.Environment;
+import android.os.FileObserver;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+import android.security.Credentials;
+import android.security.KeyStore;
import android.text.Html;
import android.text.TextUtils;
import android.util.Log;
@@ -30,26 +38,851 @@ import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.openssl.PEMWriter;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Serializable;
+import java.security.KeyStore.PasswordProtection;
+import java.security.KeyStore.PrivateKeyEntry;
+import java.security.KeyFactory;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
/**
- * The app that installs certificates to keystore. It reacts to two intents:
- * {@link CertTool#ACTION_ADD_CREDENTIAL} and
- * {@link CertTool#ACTION_INSTALL_CERT_FROM_SDCARD}.
+ * Installs certificates to the system keystore. It reacts to the
+ * {@link Credentials#INSTALL_ACTION} intent.
*/
-public class CertInstaller extends Activity
- implements DialogInterface.OnClickListener,
- DialogInterface.OnDismissListener,
+public class CertInstaller extends PreferenceActivity
+ implements Preference.OnPreferenceClickListener, FileFilter,
DialogInterface.OnCancelListener {
+ private static final String TAG = "CertInstaller";
+
+ private static final int NAME_CREDENTIAL_DIALOG = 1;
+ private static final int PKCS12_PASSWORD_DIALOG = 2;
+ private static final int PROGRESS_BAR_DIALOG = 3;
+ private static final int REQUEST_SYSTEM_INSTALL_CODE = 1;
+
+ private static final String DOWNLOAD = "download";
+
+ // dialog result
+ private static final int REOPEN = 1; // re-open the dialog
+ private static final int DONE = 2;
+ private static final int CANCELLED = 3;
+
+ private static final byte[] PKEY_MAP_KEY = "PKEY_MAP".getBytes();
+
+ private KeyStore mKeyStore = KeyStore.getInstance();
+ private View mView;
+ private int mDialogResult = REOPEN;
+
+ private boolean mIsBrowsingSdcard;
+ private SdcardMonitor mSdcardMonitor;
+ private File mCertFile;
+
+ private CredentialHelper mCredentials;
+ private Runnable mBottomHalf;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ handleIntents(getIntent());
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (mBottomHalf != null) {
+ Runnable r = mBottomHalf;
+ mBottomHalf = null;
+ r.run();
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ stopSdcardMonitor();
+ }
+
+ @Override
+ protected Dialog onCreateDialog (int id) {
+ switch (id) {
+ case PKCS12_PASSWORD_DIALOG:
+ return createPkcs12PasswordDialog();
+
+ case NAME_CREDENTIAL_DIALOG:
+ return createNameCredentialDialog();
+
+ case PROGRESS_BAR_DIALOG:
+ ProgressDialog dialog = new ProgressDialog(this);
+ dialog.setMessage(getString(R.string.extracting_pkcs12));
+ dialog.setIndeterminate(true);
+ dialog.setCancelable(false);
+ return dialog;
+
+ default:
+ return null;
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ if (requestCode == REQUEST_SYSTEM_INSTALL_CODE) {
+ if (resultCode == RESULT_OK) {
+ Log.d(TAG, "credential is added: " + mCredentials.getName());
+ Toast.makeText(this,
+ getString(R.string.cert_is_added,
+ mCredentials.getName()),
+ Toast.LENGTH_LONG).show();
+ deleteCert(mCertFile);
+ if (!mIsBrowsingSdcard) finish();
+ } else {
+ Log.d(TAG, "credential not saved, err: " + resultCode);
+ toastErrorAndFinish(R.string.cert_not_saved);
+ }
+ } else {
+ Log.w(TAG, "unknown request code: " + requestCode);
+ return;
+ }
+ }
+
+ private void handleIntents(Intent intent) {
+ if (intent == null) return;
+ String action = intent.getAction();
+
+ if (Credentials.INSTALL_ACTION.equals(action)) {
+ mCredentials = new CredentialHelper(intent);
+
+ if (!mCredentials.containsAny()) {
+ mIsBrowsingSdcard = true;
+ addPreferencesFromResource(R.xml.pick_file_pref);
+ startSdcardMonitor();
+ createFileList();
+ } else if (mCredentials.isPkcs12KeyStore()) {
+ showDialog(PKCS12_PASSWORD_DIALOG);
+ } else {
+ installAskingKeyStoreAccess();
+ }
+ }
+ }
+
+ private void installAskingKeyStoreAccess() {
+ if (mCredentials.isKeyPair()) {
+ if (isKeyStoreLocked()) {
+ unlockKeyStore();
+ return;
+ }
+ saveKeyPair();
+ finish();
+ } else {
+ X509Certificate crt = mCredentials.getUserCertificate();
+ if (crt != null) {
+ // find matched private key
+ String key = toMd5(crt.getPublicKey().getEncoded());
+ Map<String, byte[]> map = getPkeyMap();
+ byte[] privatekey = map.get(key);
+ if (privatekey != null) {
+ if (isKeyStoreLocked()) {
+ unlockKeyStore();
+ return;
+ }
+ Log.d(TAG, "found matched key: " + privatekey);
+ map.remove(key);
+ setPkeyMap(map);
+
+ mCredentials.setPrivateKey(privatekey);
+ } else {
+ Log.d(TAG, "didn't find matched private key: " + key);
+ }
+ }
+ nameCredential();
+ }
+ }
+
+ private void unlockKeyStore() {
+ mBottomHalf = new Runnable() {
+ public void run() {
+ if (!isKeyStoreLocked()) {
+ installAskingKeyStoreAccess();
+ } else {
+ toastErrorAndFinish(R.string.cert_not_saved);
+ }
+ }
+ };
+ Credentials.getInstance().unlock(this);
+ }
+
+ private void nameCredential() {
+ if (!mCredentials.readyForSystemInstall()) {
+ toastErrorAndFinish(R.string.no_cert_to_saved);
+ } else {
+ showDialog(NAME_CREDENTIAL_DIALOG);
+ }
+ }
+
+ private void saveKeyPair() {
+ byte[] privatekey = mCredentials.getData(Credentials.PRIVATE_KEY);
+ String key = toMd5(mCredentials.getData(Credentials.PUBLIC_KEY));
+ Map<String, byte[]> map = getPkeyMap();
+ map.put(key, privatekey);
+ setPkeyMap(map);
+ Log.d(TAG, "privatekey key: " + key + " --> #keys:" + map.size());
+ }
+
+ private void setPkeyMap(Map<String, byte[]> map) {
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream os = new ObjectOutputStream(baos);
+ os.writeObject(map);
+ os.close();
+ if (!mKeyStore.put(PKEY_MAP_KEY, baos.toByteArray())) {
+ Log.w(TAG, "setPkeyMap(): failed to write pkey map");
+ }
+ } catch (Exception e) {
+ // if anything wrong, we lost the private key
+ Log.w(TAG, "setPkeyMap(): " + e);
+ }
+ }
+
+ private Map<String, byte[]> getPkeyMap() {
+ byte[] bb = mKeyStore.get(PKEY_MAP_KEY);
+ if (bb != null) {
+ try {
+ ObjectInputStream is =
+ new ObjectInputStream(new ByteArrayInputStream(bb));
+ Map<String, byte[]> map = (Map<String, byte[]>) is.readObject();
+ if (map != null) return map;
+ } catch (Exception e) {
+ // if anything wrong, create a new map
+ Log.w(TAG, "getPkeyMap(): " + e);
+ }
+ }
+
+ return new MyMap();
+ }
public void onCancel(DialogInterface dialog) {
+ mDialogResult = CANCELLED;
+ }
+
+ private Dialog createPkcs12PasswordDialog() {
+ mView = View.inflate(this, R.layout.password_dialog, null);
+ mDialogResult = REOPEN;
+
+ DialogInterface.OnClickListener onClickHandler =
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON_NEGATIVE) {
+ onCancel(dialog);
+ return;
+ }
+
+ String passwd = getViewText(R.id.credential_password);
+
+ hideError();
+ if (TextUtils.isEmpty(passwd)) {
+ showError(R.string.password_empty_error);
+ } else {
+ mDialogResult = DONE;
+ }
+ }
+ };
+
+ DialogInterface.OnDismissListener onDismissHandler =
+ new DialogInterface.OnDismissListener() {
+ public void onDismiss(DialogInterface dialog) {
+ Log.d(TAG, "onDismiss() called");
+ // Original code without progress bar:
+ //if (mDialogResult == DONE) {
+ // String passwd = getViewText(R.id.credential_password);
+ // if (!mCredentials.extractFromPkcs12(passwd)) {
+ // mDialogResult = REOPEN;
+ // showError(R.string.password_error);
+ // }
+ //}
+ //enterPasswdDialogBottomHalf();
+
+ // show progress bar and extract in background thread
+ if (mDialogResult == DONE) {
+ showDialog(PROGRESS_BAR_DIALOG);
+
+ final String passwd = getViewText(R.id.credential_password);
+ new Thread(new Runnable() {
+ public void run() {
+ if (!mCredentials.extractFromPkcs12(passwd)) {
+ mDialogResult = REOPEN;
+ showError(R.string.password_error);
+ }
+ removeDialog(PROGRESS_BAR_DIALOG);
+ runOnUiThread(new Runnable() {
+ public void run() {
+ enterPasswdDialogBottomHalf();
+ }
+ });
+ }
+ }).start();
+ } else {
+ enterPasswdDialogBottomHalf();
+ }
+ }
+ };
+
+ String title = (mCertFile == null)
+ ? getString(R.string.pkcs12_password_dialog_title)
+ : getString(R.string.pkcs12_file_password_dialog_title,
+ mCertFile.getName());
+ Dialog d = new AlertDialog.Builder(this)
+ .setView(mView)
+ .setTitle(title)
+ .setPositiveButton(android.R.string.ok, onClickHandler)
+ .setNegativeButton(android.R.string.cancel, onClickHandler)
+ .setOnCancelListener(this)
+ .create();
+ d.setOnDismissListener(onDismissHandler);
+ return d;
+ }
+
+ private void enterPasswdDialogBottomHalf() {
+ if (mDialogResult == DONE) {
+ nameCredential();
+ } else if (mDialogResult == REOPEN) {
+ showDialog(PKCS12_PASSWORD_DIALOG);
+ return;
+ } else {
+ toastErrorAndFinish(R.string.cert_not_saved);
+ }
+ removeDialog(PKCS12_PASSWORD_DIALOG);
+ }
+
+ private Dialog createNameCredentialDialog() {
+ mView = View.inflate(this, R.layout.name_credential_dialog, null);
+ mDialogResult = REOPEN;
+
+ setViewText(R.id.credential_name_title, R.string.credential_name);
+ setViewText(R.id.credential_info_title, R.string.credential_info);
+ setViewText(R.id.credential_info,
+ mCredentials.getDescription().toString());
+
+ DialogInterface.OnClickListener onClickHandler =
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON_NEGATIVE) {
+ onCancel(dialog);
+ return;
+ }
+
+ hideError();
+
+ String name = getViewText(R.id.credential_name);
+ if (TextUtils.isEmpty(name)) {
+ showError(R.string.name_empty_error);
+ } else {
+ mCredentials.setName(name);
+ mDialogResult = DONE;
+ }
+ }
+ };
+
+ DialogInterface.OnDismissListener onDismissHandler =
+ new DialogInterface.OnDismissListener() {
+ public void onDismiss(DialogInterface dialog) {
+ if (mDialogResult == DONE) {
+ // install everything to system keystore
+ try {
+ startActivityForResult(
+ mCredentials.createSystemInstallIntent(),
+ REQUEST_SYSTEM_INSTALL_CODE);
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "systemInstall(): " + e);
+ toastErrorAndFinish(R.string.cert_not_saved);
+ }
+ } else if (mDialogResult == REOPEN) {
+ showDialog(NAME_CREDENTIAL_DIALOG);
+ return;
+ } else {
+ toastErrorAndFinish(R.string.cert_not_saved);
+ }
+ removeDialog(NAME_CREDENTIAL_DIALOG);
+ }
+ };
+
+ Dialog d = new AlertDialog.Builder(this)
+ .setView(mView)
+ .setTitle(R.string.name_credential_dialog_title)
+ .setPositiveButton(android.R.string.ok, onClickHandler)
+ .setNegativeButton(android.R.string.cancel, onClickHandler)
+ .setOnCancelListener(this)
+ .create();
+ d.setOnDismissListener(onDismissHandler);
+ return d;
+ }
+
+ private void setAllFilesEnabled(boolean enabled) {
+ PreferenceScreen root = getPreferenceScreen();
+ for (int i = 0, n = root.getPreferenceCount(); i < n; i++) {
+ root.getPreference(i).setEnabled(enabled);
+ }
+ }
+
+ public boolean onPreferenceClick(Preference p) {
+ File f = new File(Environment.getExternalStorageDirectory(),
+ p.getTitle().toString());
+ if (f.isDirectory()) {
+ Log.w(TAG, "impossible to pick a directory! " + f);
+ } else {
+ setAllFilesEnabled(false);
+ installFromSdCard(f);
+ }
+ return true;
+ }
+
+ private synchronized void createFileList() {
+ try {
+ getPreferenceScreen().removeAll();
+ File dir = Environment.getExternalStorageDirectory();
+ createPreferencesFor(new File(dir, DOWNLOAD));
+ createPreferencesFor(dir);
+ } catch (IOException e) {
+ // should not occur
+ Log.w(TAG, "createFileList(): " + e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void createPreferencesFor(File dir) throws IOException {
+ if ((dir == null) || !dir.isDirectory()) return;
+
+ PreferenceScreen root = getPreferenceScreen();
+ int prefixEnd = Environment.getExternalStorageDirectory()
+ .getCanonicalPath().length() + 1;
+ for (File f : dir.listFiles(this)) {
+ Preference p = new Preference(this);
+ p.setTitle(f.getCanonicalPath().substring(prefixEnd));
+ root.addPreference(p);
+ p.setOnPreferenceClickListener(this);
+ }
+ }
+
+ public boolean accept(File f) {
+ if (!f.isDirectory()) {
+ return f.getPath().endsWith(".p12");
+ } else {
+ return false;
+ }
+ }
+
+ private void toastErrorAndFinish(int msgId) {
+ if (msgId == R.string.cert_not_saved) {
+ toastErrorAndFinish(msgId, Toast.LENGTH_SHORT);
+ } else {
+ toastErrorAndFinish(msgId, Toast.LENGTH_LONG);
+ }
+ }
+
+ private void toastErrorAndFinish(int msgId, int duration) {
+ toastErrorAndFinish(getString(msgId), duration);
+ }
+
+ private void toastErrorAndFinish(String msg, int duration) {
+ Toast.makeText(this, msg, duration).show();
+
+ if (mIsBrowsingSdcard) {
+ setAllFilesEnabled(true);
+ } else {
+ finish();
+ }
+ }
+
+ private void installFromSdCard(File f) {
+ Log.d(TAG, "install cert from " + f);
+
+ mCertFile = f;
+ if (f.exists()) {
+ long length = f.length();
+ if (length < 1000000) {
+ byte[] data = readCert(f);
+ if (data == null) {
+ toastErrorAndFinish(R.string.cert_read_error);
+ return;
+ }
+ mCredentials.putData(Credentials.PKCS12, data);
+ showDialog(PKCS12_PASSWORD_DIALOG);
+ } else {
+ Log.w(TAG, "cert file is too large: " + length);
+ toastErrorAndFinish(R.string.cert_too_large_error);
+ }
+ } else {
+ Log.w(TAG, "cert file does not exist");
+ toastErrorAndFinish(R.string.cert_missing_error);
+ }
+ }
+
+ private byte[] readCert(File f) {
+ try {
+ byte[] data = new byte[(int) f.length()];
+ FileInputStream fis = new FileInputStream(f);
+ fis.read(data);
+ fis.close();
+ return data;
+ } catch (Exception e) {
+ Log.w(TAG, "cert file read error: " + e);
+ return null;
+ }
+ }
+
+ private void deleteCert(File f) {
+ if ((f != null) && !f.delete()) {
+ Log.w(TAG, "cannot delete cert: " + f);
+ }
+ }
+
+ private boolean isKeyStoreLocked() {
+ return (mKeyStore.test() != KeyStore.NO_ERROR);
+ }
+
+ private TextView showError(int messageId) {
+ TextView v = (TextView) mView.findViewById(R.id.error);
+ v.setText(messageId);
+ if (v != null) v.setVisibility(View.VISIBLE);
+ return v;
+ }
+
+ private void hide(int viewId) {
+ View v = mView.findViewById(viewId);
+ if (v != null) v.setVisibility(View.GONE);
+ }
+
+ private void hideError() {
+ hide(R.id.error);
+ }
+
+ private String getViewText(int viewId) {
+ return ((TextView) mView.findViewById(viewId)).getText().toString();
+ }
+
+ private void setViewText(int viewId, String text) {
+ TextView v = (TextView) mView.findViewById(viewId);
+ if (v != null) v.setText(text);
+ }
+
+ private void setViewText(int viewId, int textId) {
+ TextView v = (TextView) mView.findViewById(viewId);
+ if (v != null) v.setText(textId);
+ }
+
+ private void startSdcardMonitor() {
+ mSdcardMonitor = new SdcardMonitor();
+ mSdcardMonitor.startWatching();
+ }
+
+ private void stopSdcardMonitor() {
+ if (mSdcardMonitor != null) mSdcardMonitor.stopWatching();
+ }
+
+ private static String toMd5(byte[] bb) {
+ try {
+ MessageDigest algorithm = MessageDigest.getInstance("MD5");
+ algorithm.reset();
+ algorithm.update(bb);
+ return toHexString(algorithm.digest(), "");
+ } catch(NoSuchAlgorithmException e){
+ // should not occur
+ Log.w(TAG, "toMd5(): " + e);
+ throw new RuntimeException(e);
+ }
}
- public void onClick(DialogInterface dialog, int which) {
+ private static String toHexString(byte[] bb, String separator) {
+ StringBuilder hexString = new StringBuilder();
+ for (byte b : bb) {
+ hexString.append(Integer.toHexString(0xFF & b)).append(separator);
+ }
+ return hexString.toString();
}
- public void onDismiss(DialogInterface dialog) {
+ private class CredentialHelper {
+ private Bundle mBundle;
+ private String mName;
+
+ private PrivateKey mUserKey;
+ private X509Certificate mUserCert;
+ private List<X509Certificate> mCaCerts =
+ new ArrayList<X509Certificate>();
+
+ CredentialHelper(Intent intent) {
+ mBundle = intent.getExtras();
+ if (mBundle == null) {
+ mBundle = new Bundle();
+ return;
+ }
+
+ // debug
+ Log.d(TAG, "# extras: " + mBundle.size());
+ for (String key : mBundle.keySet()) {
+ Log.d(TAG, " " + key + ": " + mBundle.getByteArray(key));
+ }
+ parseCert(getData(Credentials.CERTIFICATE));
+ }
+
+ X509Certificate getUserCertificate() {
+ return mUserCert;
+ }
+
+ PrivateKey getUserKey() {
+ return mUserKey;
+ }
+
+ private void parseCert(byte[] bb) {
+ if (bb == null) return;
+ try {
+ CertificateFactory crtFac = CertificateFactory.getInstance("X.509");
+ X509Certificate crt = (X509Certificate)
+ crtFac.generateCertificate(new ByteArrayInputStream(bb));
+ if (isCa(crt)) {
+ Log.d(TAG, "got a CA cert");
+ mCaCerts.add(crt);
+ } else {
+ Log.d(TAG, "got a user cert");
+ mUserCert = crt;
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "parseCert(): " + e);
+ toastErrorAndFinish(
+ getString(R.string.intent_parse_error, e.toString()),
+ Toast.LENGTH_LONG);
+ }
+ }
+
+ private boolean isCa(X509Certificate crt) {
+ try {
+ // TODO: add a test about this
+ byte[] bc = crt.getExtensionValue("2.5.29.19");
+ Object o = new ASN1InputStream(bc).readObject();
+ byte[] bc2 = ((DEROctetString) o).getOctets();
+ Object o2 = new ASN1InputStream(bc2).readObject();
+ return new BasicConstraints((ASN1Sequence) o2).isCA();
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ boolean isPkcs12KeyStore() {
+ return mBundle.containsKey(Credentials.PKCS12);
+ }
+
+ boolean isKeyPair() {
+ return mBundle.containsKey(Credentials.PUBLIC_KEY)
+ && mBundle.containsKey(Credentials.PRIVATE_KEY);
+ }
+
+ boolean readyForSystemInstall() {
+ return ((mUserKey != null) || (mUserCert != null)
+ || !mCaCerts.isEmpty());
+ }
+
+ private void setPrivateKey(byte[] bytes) {
+ try {
+ KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+ mUserKey = keyFactory.generatePrivate(
+ new PKCS8EncodedKeySpec(bytes));
+ } catch (Exception e) {
+ // should not occur
+ Log.w(TAG, "setPrivateKey(): " + e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ // notes: only this method checks if mBundle is null
+ boolean containsAny() {
+ if (mBundle == null) return false;
+ return !mBundle.isEmpty();
+ }
+
+ byte[] getData(String key) {
+ return mBundle.getByteArray(key);
+ }
+
+ void putData(String key, byte[] data) {
+ mBundle.putByteArray(key, data);
+ }
+
+ CharSequence getDescription() {
+ // TODO: create more descriptive string
+ StringBuilder sb = new StringBuilder();
+ String nl = "<br>";
+ if (mUserKey != null) {
+ sb.append(getString(R.string.one_userkey)).append(nl);
+ }
+ if (mUserCert != null) {
+ sb.append(getString(R.string.one_usercrt)).append(nl);
+ }
+ int n = mCaCerts.size();
+ if (n > 0) {
+ if (n == 1) {
+ sb.append(getString(R.string.one_cacrt));
+ } else {
+ sb.append(getString(R.string.n_cacrts, n));
+ }
+ }
+ return Html.fromHtml(sb.toString());
+ }
+
+ void setName(String name) {
+ mName = name;
+ }
+
+ String getName() {
+ return mName;
+ }
+
+ Intent createSystemInstallIntent() {
+ Intent i = new Intent(Credentials.SYSTEM_INSTALL_ACTION);
+ if (mUserKey != null) {
+ i.putExtra(Credentials.USER_PRIVATE_KEY + mName,
+ convertToPem(mUserKey));
+ }
+ if (mUserCert != null) {
+ i.putExtra(Credentials.USER_CERTIFICATE + mName,
+ convertToPem(mUserCert));
+ }
+ if (!mCaCerts.isEmpty()) {
+ Object[] cacrts = (Object[])
+ mCaCerts.toArray(new X509Certificate[mCaCerts.size()]);
+ i.putExtra(Credentials.CA_CERTIFICATE + mName,
+ convertToPem(cacrts));
+ }
+ return i;
+ }
+
+ boolean extractFromPkcs12(String passwd) {
+ try {
+ return extractFromPkcs12Internal(passwd);
+ } catch (Exception e) {
+ Log.w(TAG, "extractFromPkcs12(): " + e);
+ return false;
+ }
+ }
+
+ private boolean extractFromPkcs12Internal(String passwd)
+ throws Exception {
+ // TODO: add test about this
+ java.security.KeyStore keystore =
+ java.security.KeyStore.getInstance("PKCS12");
+ PasswordProtection pp =
+ new PasswordProtection(passwd.toCharArray());
+ keystore.load(new ByteArrayInputStream(getData(Credentials.PKCS12)),
+ pp.getPassword());
+
+ Enumeration<String> aliases = keystore.aliases();
+ if (!aliases.hasMoreElements()) return false;
+
+ String alias = aliases.nextElement();
+ Log.d(TAG, "extracted alias = " + alias);
+ PrivateKeyEntry entry =
+ (PrivateKeyEntry) keystore.getEntry(alias, pp);
+ mUserKey = entry.getPrivateKey();
+ mUserCert = (X509Certificate) entry.getCertificate();
+
+ Certificate[] crts = entry.getCertificateChain();
+ List<X509Certificate> caCerts = mCaCerts =
+ new ArrayList<X509Certificate>(crts.length);
+ for (Certificate c : crts) {
+ X509Certificate crt = (X509Certificate) c;
+ if (isCa(crt)) caCerts.add(crt);
+ }
+ Log.d(TAG, "caCerts.length = " + mCaCerts.size());
+ return true;
+ }
+
+ private byte[] convertToPem(Object... oo) {
+ try {
+ ByteArrayOutputStream bao = new ByteArrayOutputStream();
+ OutputStreamWriter osw = new OutputStreamWriter(bao);
+ PEMWriter pw = new PEMWriter(osw);
+ for (Object o : oo) pw.writeObject(o);
+ pw.close();
+ return bao.toByteArray();
+ } catch (IOException e) {
+ // should not occur
+ Log.w(TAG, "convertToPem(): " + e);
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ private static class MyMap extends LinkedHashMap<String, byte[]>
+ implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ protected boolean removeEldestEntry(Map.Entry eldest) {
+ return (size() > 3);
+ }
+ }
+
+ private class SdcardMonitor {
+ FileObserver mRootMonitor;
+ FileObserver mDownloadMonitor;
+
+ SdcardMonitor() {
+ File root = Environment.getExternalStorageDirectory();
+ mRootMonitor = new FileObserver(root.getPath()) {
+ @Override
+ public void onEvent(int evt, String path) {
+ commonHandler(evt, path);
+ }
+ };
+
+ File download = new File(root, DOWNLOAD);
+ mDownloadMonitor = new FileObserver(download.getPath()) {
+ @Override
+ public void onEvent(int evt, String path) {
+ commonHandler(evt, path);
+ }
+ };
+ }
+
+ private void commonHandler(int evt, String path) {
+ switch (evt) {
+ case FileObserver.CREATE:
+ case FileObserver.DELETE:
+ if (path.endsWith(".p12")) createFileList();
+ break;
+ }
+ };
+
+ void startWatching() {
+ mRootMonitor.startWatching();
+ mDownloadMonitor.startWatching();
+ }
+
+ void stopWatching() {
+ mRootMonitor.stopWatching();
+ mDownloadMonitor.stopWatching();
+ }
}
}