package com.zeapo.pwdstore.crypto; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.text.ParseException; import java.util.ArrayList; import org.apache.commons.io.FileUtils; import org.eclipse.jgit.util.StringUtils; import org.openintents.openpgp.IOpenPgpService; import org.openintents.openpgp.OpenPgpError; import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpServiceConnection; import org.openintents.openpgp.util.OpenPgpUtils; import android.app.ActionBar; import android.app.Activity; import android.app.AlertDialog; import android.app.PendingIntent; import android.app.ProgressDialog; import android.content.ClipData; import android.content.ClipboardManager; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentSender; import android.content.SharedPreferences; import android.os.AsyncTask; import android.os.Bundle; import android.os.SystemClock; import android.preference.PreferenceManager; import android.support.v7.app.ActionBarActivity; import android.text.TextUtils; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import com.zeapo.pwdstore.R; import com.zeapo.pwdstore.UserPreference; import com.zeapo.pwdstore.utils.PasswordRepository; public class PgpHandler extends ActionBarActivity implements OpenPgpServiceConnection.OnBound{ private OpenPgpServiceConnection mServiceConnection; private String keyIDs = ""; private String accountName = ""; SharedPreferences settings; private Activity activity; ClipboardManager clipboard; private boolean registered; public static final int REQUEST_CODE_SIGN = 9910; public static final int REQUEST_CODE_ENCRYPT = 9911; public static final int REQUEST_CODE_SIGN_AND_ENCRYPT = 9912; public static final int REQUEST_CODE_DECRYPT_AND_VERIFY = 9913; public static final int REQUEST_CODE_GET_KEY = 9914; public static final int REQUEST_CODE_GET_KEY_IDS = 9915; public final class Constants { public static final String TAG = "Keychain"; } private String filePath; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.activity = this; this.clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); // some persistance settings = PreferenceManager.getDefaultSharedPreferences(this); String providerPackageName = settings.getString("openpgp_provider_list", ""); accountName = settings.getString("openpgp_account_name", ""); keyIDs = settings.getString("openpgp_key_ids", ""); registered = false; if (TextUtils.isEmpty(providerPackageName)) { Toast.makeText(this, this.getResources().getString(R.string.provider_toast_text), Toast.LENGTH_LONG).show(); Intent intent = new Intent(this, UserPreference.class); startActivity(intent); // a small hack to avoid eternal loop later, have to be solved via a startactivityforresult() setResult(RESULT_CANCELED); finish(); } else { // bind to service mServiceConnection = new OpenPgpServiceConnection( PgpHandler.this, providerPackageName, this); mServiceConnection.bindToService(); registered = true; getSupportActionBar().setDisplayHomeAsUpEnabled(true); } } @Override public void onStop(){ super.onStop(); if (this.registered && this.mServiceConnection.isBound()) try { this.mServiceConnection.unbindFromService(); } catch (Exception e){ } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.pgp_handler, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); switch (id) { case android.R.id.home: setResult(RESULT_CANCELED); finish(); return true; case R.id.copy_password: copyToClipBoard(); } return super.onOptionsItemSelected(item); } public void copyToClipBoard() { ClipData clip = ClipData.newPlainText("pgp_handler_result_pm", ((TextView) findViewById(R.id.crypto_password_show)).getText()); clipboard.setPrimaryClip(clip); showToast(this.getResources().getString(R.string.clipboard_beginning_toast_text) + Integer.parseInt(settings.getString("general_show_time", "45")) + this.getResources().getString(R.string.clipboard_ending_toast_text)); } public void handleClick(View view) { switch (view.getId()) { case R.id.crypto_show_button: decryptAndVerify(new Intent()); break; case R.id.crypto_confirm_add: encrypt(new Intent()); break; case R.id.crypto_cancel_add: finish(); break; case R.id.crypto_delete_button: // deletePassword(); break; case R.id.crypto_get_key_ids: getKeyIds(new Intent()); break; default: // should not happen } } private void handleError(final OpenPgpError error) { runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(PgpHandler.this, "onError id:" + error.getErrorId() + "\n\n" + error.getMessage(), Toast.LENGTH_LONG).show(); Log.e(Constants.TAG, "onError getErrorId:" + error.getErrorId()); Log.e(Constants.TAG, "onError getMessage:" + error.getMessage()); } }); } private void showToast(final String message) { runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(PgpHandler.this, message, Toast.LENGTH_SHORT).show(); } }); } public class DelayShow extends AsyncTask { ProgressBar pb; @Override protected void onPreExecute() { LinearLayout container = (LinearLayout) findViewById(R.id.crypto_container); container.setVisibility(View.VISIBLE); TextView extraText = (TextView) findViewById(R.id.crypto_extra_show); if (extraText.getText().length() != 0) ((LinearLayout) findViewById(R.id.crypto_extra_show_layout)).setVisibility(View.VISIBLE); this.pb = (ProgressBar) findViewById(R.id.pbLoading); // Make Show Time a user preference // kLeZ: Changed to match the default for pass SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(container.getContext()); int SHOW_TIME; try { SHOW_TIME = Integer.parseInt(settings.getString("general_show_time", "45")); } catch (NumberFormatException e) { SHOW_TIME = 45; } this.pb.setMax(SHOW_TIME); } @Override protected Boolean doInBackground(Void... params) { while (this.pb.getProgress() < this.pb.getMax()) { SystemClock.sleep(1000); publishProgress(this.pb.getProgress() + 1); } return true; } @Override protected void onPostExecute(Boolean b) { ClipData clip = ClipData.newPlainText("pgp_handler_result_pm", "MyPasswordIsDaBest!"); clipboard.setPrimaryClip(clip); //clear password ((TextView) findViewById(R.id.crypto_password_show)).setText(""); ((TextView) findViewById(R.id.crypto_extra_show)).setText(""); findViewById(R.id.crypto_extra_show_layout).setVisibility(View.INVISIBLE); findViewById(R.id.crypto_container).setVisibility(View.INVISIBLE); activity.setResult(RESULT_CANCELED); activity.finish(); } @Override protected void onProgressUpdate(Integer... values) { this.pb.setProgress(values[0]); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); Log.d(Constants.TAG, "onActivityResult resultCode: " + resultCode); // try again after user interaction if (resultCode == RESULT_OK) { /* * The data originally given to one of the methods above, is again * returned here to be used when calling the method again after user * interaction. The Intent now also contains results from the user * interaction, for example selected key ids. */ switch (requestCode) { case REQUEST_CODE_ENCRYPT: { encrypt(data); break; } case REQUEST_CODE_DECRYPT_AND_VERIFY: { decryptAndVerify(data); break; } case REQUEST_CODE_GET_KEY_IDS: getKeyIds(data); break; } } else if (resultCode == RESULT_CANCELED) { setResult(RESULT_CANCELED, data); finish(); } } public class PgpCallback implements OpenPgpApi.IOpenPgpCallback { boolean returnToCiphertextField; ByteArrayOutputStream os; int requestCode; private PgpCallback(boolean returnToCiphertextField, ByteArrayOutputStream os, int requestCode) { this.returnToCiphertextField = returnToCiphertextField; this.os = os; this.requestCode = requestCode; } @Override public void onReturn(Intent result) { switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { case OpenPgpApi.RESULT_CODE_SUCCESS: { // encrypt/decrypt/sign/verify if (requestCode == REQUEST_CODE_DECRYPT_AND_VERIFY && os != null) { try { Log.d(OpenPgpApi.TAG, "result: " + os.toByteArray().length + " str=" + os.toString("UTF-8")); if (returnToCiphertextField) { findViewById(R.id.crypto_container).setVisibility(View.VISIBLE); String[] passContent = os.toString("UTF-8").split("\n"); ((TextView) findViewById(R.id.crypto_password_show)) .setText(passContent[0]); String extraContent = os.toString("UTF-8").replaceFirst(".*\n", ""); if (extraContent.length() != 0) { ((TextView) findViewById(R.id.crypto_extra_show)) .setText(extraContent); } new DelayShow().execute(); if (settings.getBoolean("copy_on_decrypt", true)) { copyToClipBoard(); } } else { Log.d("PGPHANDLER", "Error message after decrypt : " + os.toString()); } } catch (UnsupportedEncodingException e) { Log.e(Constants.TAG, "UnsupportedEncodingException", e); } } // encrypt if (requestCode == REQUEST_CODE_ENCRYPT && os != null) { try { String path = getIntent().getExtras().getString("FILE_PATH") + "/" + ((EditText) findViewById(R.id.crypto_password_file_edit)).getText().toString() + ".gpg"; OutputStream outputStream = FileUtils.openOutputStream(new File(path)); outputStream.write(os.toByteArray()); Intent data = new Intent(); data.putExtra("CREATED_FILE", path); data.putExtra("NAME", ((EditText) findViewById(R.id.crypto_password_file_edit)).getText().toString()); setResult(RESULT_OK, data); finish(); } catch (Exception e) { Log.e(Constants.TAG, "UnsupportedEncodingException", e); } } // get key ids if (result.hasExtra(OpenPgpApi.RESULT_KEY_IDS)) { long[] ids = result.getLongArrayExtra(OpenPgpApi.RESULT_KEY_IDS); ArrayList keys = new ArrayList(); for (int i = 0; i < ids.length; i++) { keys.add(OpenPgpUtils.convertKeyIdToHex(ids[i])); } keyIDs = StringUtils.join(keys, ", "); if (!keyIDs.isEmpty()) { String mKeys = keyIDs.split(",").length > 1 ? keyIDs : keyIDs.split(",")[0]; // ((TextView) findViewById(R.id.crypto_key_ids)).setText(mKeys); settings.edit().putString("openpgp_key_ids", keyIDs).apply(); Log.i("PGP", mKeys); } setResult(RESULT_OK); finish(); } break; } case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: { Log.i("PgpHandler", "RESULT_CODE_USER_INTERACTION_REQUIRED"); PendingIntent pi = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT); try { PgpHandler.this.startIntentSenderForResult(pi.getIntentSender(), requestCode, null, 0, 0, 0); } catch (IntentSender.SendIntentException e) { Log.e(Constants.TAG, "SendIntentException", e); } break; } case OpenPgpApi.RESULT_CODE_ERROR: { // TODO show what kind of error it is /* For example: * No suitable key found -> no key in OpenKeyChain * * Check in open-pgp-lib how their definitions and error code */ OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR); handleError(error); break; } } } } public void getKeyIds(Intent data) { accountName = settings.getString("openpgp_account_name", ""); if (accountName.isEmpty()) showToast(this.getResources().getString(R.string.name_settings_toast_text)); data.setAction(OpenPgpApi.ACTION_GET_KEY_IDS); data.putExtra(OpenPgpApi.EXTRA_USER_IDS, new String[]{accountName.isEmpty() ? "default" : accountName}); OpenPgpApi api = new OpenPgpApi(this, mServiceConnection.getService()); api.executeApiAsync(data, null, null, new PgpCallback(false, null, PgpHandler.REQUEST_CODE_GET_KEY_IDS)); } public void decryptAndVerify(Intent data) { data.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY); data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); try { InputStream is = FileUtils.openInputStream(new File(getIntent().getExtras().getString("FILE_PATH"))); ByteArrayOutputStream os = new ByteArrayOutputStream(); OpenPgpApi api = new OpenPgpApi(this, mServiceConnection.getService()); api.executeApiAsync(data, is, os, new PgpCallback(true, os, REQUEST_CODE_DECRYPT_AND_VERIFY)); } catch (Exception e) { e.printStackTrace(); } } public void encrypt(Intent data) { accountName = settings.getString("openpgp_account_name", ""); if (accountName.isEmpty()) { new AlertDialog.Builder(this) .setMessage(this.getResources().getString(R.string.account_settings_dialog_text)) .setTitle(this.getResources().getString(R.string.account_settings_dialog_title)) .setPositiveButton(this.getResources().getString(R.string.dialog_ok), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { try { Intent intent = new Intent(getApplicationContext(), UserPreference.class); startActivity(intent); } catch (Exception e) { System.out.println("Exception caught :("); e.printStackTrace(); } } }).setNegativeButton(this.getResources().getString(R.string.dialog_no), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { // Do nothing... } }).show(); } else { data.setAction(OpenPgpApi.ACTION_ENCRYPT); data.putExtra(OpenPgpApi.EXTRA_USER_IDS, new String[]{accountName}); data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); String name = ((EditText) findViewById(R.id.crypto_password_file_edit)).getText().toString(); String pass = ((EditText) findViewById(R.id.crypto_password_edit)).getText().toString(); String extra = ((EditText) findViewById(R.id.crypto_extra_edit)).getText().toString(); if (name.isEmpty()) { showToast(this.getResources().getString(R.string.file_toast_text)); return; } if (pass.isEmpty() && extra.isEmpty()) { showToast(this.getResources().getString(R.string.empty_toast_text)); return; } ByteArrayInputStream is; try { is = new ByteArrayInputStream((pass + "\n" + extra).getBytes("UTF-8")); ByteArrayOutputStream os = new ByteArrayOutputStream(); OpenPgpApi api = new OpenPgpApi(this, mServiceConnection.getService()); api.executeApiAsync(data, is, os, new PgpCallback(true, os, REQUEST_CODE_ENCRYPT)); } catch (Exception e) { e.printStackTrace(); } } } @Override public void onBound(IOpenPgpService service) { Log.i("PGP", "ISBOUND!!"); Bundle extra = getIntent().getExtras(); if (extra.getString("Operation").equals("DECRYPT")) { setContentView(R.layout.decrypt_layout); ((TextView) findViewById(R.id.crypto_password_file)).setText(extra.getString("NAME")); String cat = new File(extra.getString("FILE_PATH").replace(PasswordRepository.getWorkTree().getAbsolutePath(), "")) .getParentFile().getName(); ((TextView) findViewById(R.id.crypto_password_category)).setText(cat + "/"); decryptAndVerify(new Intent()); } else if (extra.getString("Operation").equals("ENCRYPT")) { setContentView(R.layout.encrypt_layout); String cat = extra.getString("FILE_PATH"); cat = cat.replace(PasswordRepository.getWorkTree().getAbsolutePath(), ""); cat = cat + "/"; ((TextView) findViewById(R.id.crypto_password_category)).setText(cat); } else if (extra.getString("Operation").equals("GET_KEY_ID")) { getKeyIds(new Intent()); // setContentView(R.layout.key_id); // if (!keyIDs.isEmpty()) { // String keys = keyIDs.split(",").length > 1 ? keyIDs : keyIDs.split(",")[0]; // ((TextView) findViewById(R.id.crypto_key_ids)).setText(keys); // } } } @Override public void onError(Exception e) { } }