| /* |
| * Copyright (C) 2009 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| 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; |
| 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; |
| |
| /** |
| * Installs certificates to the system keystore. It reacts to the |
| * {@link Credentials#INSTALL_ACTION} intent. |
| */ |
| 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()) { |
| unlockKeyStoreWithBottomHalf(); |
| 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()) { |
| unlockKeyStoreWithBottomHalf(); |
| 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 unlockKeyStoreWithBottomHalf() { |
| 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[] bytes = mKeyStore.get(PKEY_MAP_KEY); |
| if (bytes != null) { |
| try { |
| ObjectInputStream is = |
| new ObjectInputStream(new ByteArrayInputStream(bytes)); |
| 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) { |
| if (isFinishing()) mDialogResult = CANCELLED; |
| |
| // 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 certs in a 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 (isFinishing()) mDialogResult = CANCELLED; |
| |
| 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 pref) { |
| File file = new File(Environment.getExternalStorageDirectory(), |
| pref.getTitle().toString()); |
| if (file.isDirectory()) { |
| Log.w(TAG, "impossible to pick a directory! " + file); |
| } else { |
| setAllFilesEnabled(false); |
| installFromSdCard(file); |
| } |
| return true; |
| } |
| |
| private void createFileList() { |
| if (isFinishing()) { |
| Log.d(TAG, "finishing, exit createFileList()"); |
| return; |
| } |
| try { |
| PreferenceScreen root = getPreferenceScreen(); |
| root.removeAll(); |
| File dir = Environment.getExternalStorageDirectory(); |
| createPreferencesFor(new File(dir, DOWNLOAD)); |
| createPreferencesFor(dir); |
| if (root.getPreferenceCount() == 0) { |
| Toast.makeText(this, R.string.no_pkcs12_found, |
| Toast.LENGTH_SHORT).show(); |
| } |
| } 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 file) { |
| if (!file.isDirectory()) { |
| return file.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 file) { |
| Log.d(TAG, "install cert from " + file); |
| |
| mCertFile = file; |
| if (file.exists()) { |
| long length = file.length(); |
| if (length < 1000000) { |
| byte[] data = readCert(file); |
| 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 file) { |
| try { |
| byte[] data = new byte[(int) file.length()]; |
| FileInputStream fis = new FileInputStream(file); |
| fis.read(data); |
| fis.close(); |
| return data; |
| } catch (Exception e) { |
| Log.w(TAG, "cert file read error: " + e); |
| return null; |
| } |
| } |
| |
| private void deleteCert(File file) { |
| if ((file != null) && !file.delete()) { |
| Log.w(TAG, "cannot delete cert: " + file); |
| } |
| } |
| |
| private boolean isKeyStoreLocked() { |
| return (mKeyStore.test() != KeyStore.NO_ERROR); |
| } |
| |
| private TextView showError(int msgId) { |
| TextView v = (TextView) mView.findViewById(R.id.error); |
| v.setText(msgId); |
| 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() { |
| if (mSdCardMonitor == null) mSdCardMonitor = new SdCardMonitor(); |
| mSdCardMonitor.startWatching(); |
| } |
| |
| private void stopSdCardMonitor() { |
| if (mSdCardMonitor != null) mSdCardMonitor.stopWatching(); |
| } |
| |
| private static String toMd5(byte[] bytes) { |
| try { |
| MessageDigest algorithm = MessageDigest.getInstance("MD5"); |
| algorithm.reset(); |
| algorithm.update(bytes); |
| return toHexString(algorithm.digest(), ""); |
| } catch(NoSuchAlgorithmException e){ |
| // should not occur |
| Log.w(TAG, "toMd5(): " + e); |
| throw new RuntimeException(e); |
| } |
| } |
| |
| private static String toHexString(byte[] bytes, String separator) { |
| StringBuilder hexString = new StringBuilder(); |
| for (byte b : bytes) { |
| hexString.append(Integer.toHexString(0xFF & b)).append(separator); |
| } |
| return hexString.toString(); |
| } |
| |
| 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[] bytes) { |
| if (bytes == null) return; |
| try { |
| CertificateFactory crtFactory = |
| CertificateFactory.getInstance("X.509"); |
| X509Certificate crt = (X509Certificate) |
| crtFactory.generateCertificate( |
| new ByteArrayInputStream(bytes)); |
| 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[] basicConstraints = crt.getExtensionValue("2.5.29.19"); |
| Object obj = new ASN1InputStream(basicConstraints).readObject(); |
| basicConstraints = ((DEROctetString) obj).getOctets(); |
| obj = new ASN1InputStream(basicConstraints).readObject(); |
| return new BasicConstraints((ASN1Sequence) obj).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 newline = "<br>"; |
| if (mUserKey != null) { |
| sb.append(getString(R.string.one_userkey)).append(newline); |
| } |
| if (mUserCert != null) { |
| sb.append(getString(R.string.one_usercrt)).append(newline); |
| } |
| 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 intent = new Intent(Credentials.SYSTEM_INSTALL_ACTION); |
| if (mUserKey != null) { |
| intent.putExtra(Credentials.USER_PRIVATE_KEY + mName, |
| convertToPem(mUserKey)); |
| } |
| if (mUserCert != null) { |
| intent.putExtra(Credentials.USER_CERTIFICATE + mName, |
| convertToPem(mUserCert)); |
| } |
| if (!mCaCerts.isEmpty()) { |
| Object[] cacrts = (Object[]) |
| mCaCerts.toArray(new X509Certificate[mCaCerts.size()]); |
| intent.putExtra(Credentials.CA_CERTIFICATE + mName, |
| convertToPem(cacrts)); |
| } |
| return intent; |
| } |
| |
| 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 passwdProtection = |
| new PasswordProtection(passwd.toCharArray()); |
| keystore.load(new ByteArrayInputStream(getData(Credentials.PKCS12)), |
| passwdProtection.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, passwdProtection); |
| mUserKey = entry.getPrivateKey(); |
| mUserCert = (X509Certificate) entry.getCertificate(); |
| |
| Certificate[] crts = entry.getCertificateChain(); |
| Log.d(TAG, "# certs extracted = " + crts.length); |
| 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, "# ca certs extracted = " + mCaCerts.size()); |
| return true; |
| } |
| |
| private byte[] convertToPem(Object... objects) { |
| try { |
| ByteArrayOutputStream bao = new ByteArrayOutputStream(); |
| OutputStreamWriter osw = new OutputStreamWriter(bao); |
| PEMWriter pw = new PEMWriter(osw); |
| for (Object o : objects) 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) { |
| // Note: one key takes about 1300 bytes in the keystore, so be |
| // cautious about allowing more outstanding keys in the map that |
| // may go beyond keystore's max space for one entry. |
| 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")) { |
| runOnUiThread(new Runnable() { |
| public void run() { |
| createFileList(); |
| } |
| }); |
| } |
| break; |
| } |
| }; |
| |
| void startWatching() { |
| mRootMonitor.startWatching(); |
| mDownloadMonitor.startWatching(); |
| } |
| |
| void stopWatching() { |
| mRootMonitor.stopWatching(); |
| mDownloadMonitor.stopWatching(); |
| } |
| } |
| } |