Android Open Source - vecna Vecna






From Project

Back to project page vecna.

License

The source code is released under:

Copyright ? 2014 Alan Berndt Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Softwar...

If you think the Android project vecna listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/* Copyright (c) 2014 Alan Berndt
 *//from   w  ww .  j a v a  2 s. co  m
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

package org.eatabrick.vecna;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.util.Iterator;

import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.spongycastle.openpgp.PGPCompressedData;
import org.spongycastle.openpgp.PGPEncryptedDataList;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPLiteralData;
import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPPublicKeyEncryptedData;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRingCollection;
import org.spongycastle.openpgp.PGPUtil;

import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.ClipData;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.InputType;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import android.support.v4.view.MenuItemCompat;
import android.support.v7.widget.SearchView;

public class Vecna extends ActionBarListActivity implements SearchView.OnQueryTextListener {
  private final static String TAG = "Vecna";

  private PasswordEntryAdapter adapter;
  private SharedPreferences settings;
  private String passphrase = "";

  private String extraInformation = "";

  private class ReadEntriesTask extends AsyncTask<String, Integer, Integer> {
    ProgressDialog progress;

    protected void onPreExecute() {
      adapter.clear();
      adapter.notifyDataSetChanged();
      adapter.setNotifyOnChange(false);

      extraInformation = "";

      progress = new ProgressDialog(Vecna.this);
      progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
      progress.setMessage(getString(R.string.progress_initial));
      progress.setCancelable(false);
      progress.setMax(6);

      progress.show();
    }

    protected Integer doInBackground(String... string) {
      PGPSecretKeyRingCollection keyring = null;
      FileInputStream dataStream = null;

      publishProgress(R.string.progress_keyfile);
      try {
        File keyFile = new File(settings.getString("key_file", ""));
        FileInputStream keyStream = new FileInputStream(keyFile);
        keyring = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(keyStream));
      } catch (Exception e) {
        return R.string.error_key_file_not_found;
      }

      publishProgress(R.string.progress_passfile);
      try {
        File dataFile = new File(settings.getString("passwords", ""));
        dataStream = new FileInputStream(dataFile);
      } catch (Exception e) {
        return R.string.error_password_file_not_found;
      }

      try {
        PGPObjectFactory factory;

        publishProgress(R.string.progress_find_data);

        factory = new PGPObjectFactory(PGPUtil.getDecoderStream(dataStream));
        PGPEncryptedDataList dataList = null;

        Object o;
        while ((o = factory.nextObject()) != null) {
          if (o instanceof PGPEncryptedDataList) {
            dataList = (PGPEncryptedDataList) o;
            break;
          }
        }

        if (dataList == null) return R.string.error_no_data_in_file;

        publishProgress(R.string.progress_find_key);

        PGPSecretKey keySecret = null;
        PGPPrivateKey keyPrivate = null;

        PGPPublicKeyEncryptedData dataEncrypted = null;
        Iterator it = dataList.getEncryptedDataObjects();
        while (keyPrivate == null && it.hasNext()) {
          dataEncrypted = (PGPPublicKeyEncryptedData) it.next();

          keySecret = keyring.getSecretKey(dataEncrypted.getKeyID());
          keyPrivate = keySecret.extractPrivateKey(string[0].toCharArray(), "SC");
        }

        if (keyPrivate == null) return R.string.error_secret_key_missing;

        publishProgress(R.string.progress_decrypt);

        factory = new PGPObjectFactory(dataEncrypted.getDataStream(keyPrivate, "SC"));
        ByteArrayOutputStream dataFinal = new ByteArrayOutputStream();
        if (!populateStreamFromFactory(factory, dataFinal)) {
          return R.string.error_encryption;
        }

        publishProgress(R.string.progress_parse);

        String[] lines = dataFinal.toString().split("\n");
        for (int i = 0; i < lines.length; ++i) {
          adapter.add(new Entry(lines[i]));
        }
      } catch (PGPException e) {
        e.printStackTrace();
        if (e.getCause() instanceof NoSuchAlgorithmException) {
          extraInformation = e.getCause().getLocalizedMessage();
          return R.string.error_algorithm;
        }
        return R.string.error_pgp_key;
      } catch (Exception e) {
        e.printStackTrace();
        return R.string.error_unknown;
      }

      return 0;
    }

    protected void onProgressUpdate(Integer... messages) {
      progress.setMessage(getString(messages[0]));
      progress.incrementProgressBy(1);
    }

    protected void onPostExecute(Integer result) {
      progress.dismiss();

      adapter.setNotifyOnChange(true);
      adapter.notifyDataSetChanged();

      if (result > 0) {
        AlertDialog.Builder builder = new AlertDialog.Builder(Vecna.this);

        builder.setTitle(getString(R.string.error));
        builder.setMessage(String.format(getString(result), extraInformation));
        builder.setCancelable(false);
        builder.setPositiveButton(android.R.string.ok, null);

        builder.show();
      }

      // reset passphrase if they enter it incorrectly
      if (result == R.string.error_pgp_key || result == R.string.error_algorithm) {
        passphrase = "";
        setEmptyText(R.string.locked);
      } else {
        setEmptyText(R.string.empty);
      }

      supportInvalidateOptionsMenu();
      findViewById(android.R.id.list).requestFocus();
    }
  }

  @Override public boolean onQueryTextChange(String newText) {
    adapter.getFilter().filter(newText);
    return true;
  }

  @Override public boolean onQueryTextSubmit(String query) {
    return true;
  }

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    settings = PreferenceManager.getDefaultSharedPreferences(getBaseContext());

    adapter = new PasswordEntryAdapter(this);
    adapter.setNotifyOnChange(false);
    setListAdapter(adapter);

    Security.addProvider(new BouncyCastleProvider());

    if (savedInstanceState != null) {
      passphrase = savedInstanceState.getString("passphrase");
      adapter.populate(savedInstanceState.getStringArray("entries"));
      adapter.notifyDataSetChanged();
    }

    getListView().setLongClickable(true);
    getListView().setOnItemLongClickListener(new OnItemLongClickListener() {
      public boolean onItemLongClick(AdapterView<?> parent, View v, int pos, long id) {
        onListItemLongClick(parent, v, pos, id);
        return true;
      }
    });
  }

  @Override public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.main, menu);

    SearchView search = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.search));

    if (search != null) {
      search.setOnQueryTextListener(this);
      search.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
    } else {
      Log.d(TAG, "Couldn't find search item");
    }

    return true;
  }

  @Override public boolean onPrepareOptionsMenu(Menu menu) {
    boolean unlocked = !isLocked();
    ((MenuItem) menu.findItem(R.id.search)).setVisible(unlocked);
    ((MenuItem) menu.findItem(R.id.refresh)).setVisible(unlocked);
    ((MenuItem) menu.findItem(R.id.lock)).setVisible(unlocked);
    return true;
  }

  @Override public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
      case R.id.lock:
        // clear the passphrase and passwords from memory
        passphrase = "";
        setEmptyText(R.string.locked);
        adapter.clear();
        adapter.notifyDataSetChanged();
        supportInvalidateOptionsMenu();
        return true;
      case R.id.refresh:
        // reload the password file
        updateEntries();
        return true;
      case R.id.settings:
        Intent settingsIntent = new Intent(this, Preferences.class);
        startActivityForResult(settingsIntent, 0);
        return true;
      case R.id.help:
        Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/bentglasstube/vecna/blob/master/README.md"));
        startActivity(browserIntent);
        return true;
      default:
        return super.onOptionsItemSelected(item);
    }
  }

  @Override protected void onResume() {
    super.onResume();

    // Automatically show passwords on launch
    if (adapter.getCount() == 0) updateEntries();
  }

  @Override protected void onListItemClick(ListView parent, View v, int pos, long id) {
    copyPassword((Entry) adapter.getItem(pos));
  }

  public void emptyClicked(View v) {
    // get the passphrase if we don't know it
    if (isLocked()) getPassphrase();
  }

  protected void onListItemLongClick(AdapterView parent, View v, int pos, long id) {
    // show a crazy dialog for the entry
    final Entry entry = (Entry) adapter.getItem(pos);

    AlertDialog.Builder builder = new AlertDialog.Builder(this);

    LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
    final View layout = inflater.inflate(R.layout.entry, (ViewGroup) findViewById(R.id.layout_root));

    ((TextView) layout.findViewById(R.id.account )).setText(entry.account);
    ((TextView) layout.findViewById(R.id.user    )).setText(entry.user);
    ((TextView) layout.findViewById(R.id.password)).setText(entry.password);

    builder.setView(layout);

    builder.setPositiveButton(R.string.show_entry_copy, new DialogInterface.OnClickListener() {
      public void onClick(DialogInterface dialog, int id) {
        copyPassword(entry);
      }
    });

    builder.setNegativeButton(R.string.show_entry_close, null);
    builder.setNeutralButton(R.string.show_entry_show, null);

    final AlertDialog dialog = builder.create();

    dialog.setOnShowListener(new DialogInterface.OnShowListener() {
      public void onShow(DialogInterface dialogInterface) {
        final Button show = dialog.getButton(AlertDialog.BUTTON_NEUTRAL);
        show.setOnClickListener(new View.OnClickListener() {
          public void onClick(View view) {
            EditText password = (EditText) layout.findViewById(R.id.password);

            if (show.getText().equals(getString(R.string.show_entry_show))) {
              Log.d(TAG, "Show clicked");
              password.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
              show.setText(R.string.show_entry_hide);
            } else {
              Log.d(TAG, "Hide clicked");
              password.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
              show.setText(R.string.show_entry_show);
            }
          }
        });
      }
    });

    dialog.show();
  }

  @Override public void onSaveInstanceState(Bundle savedInstanceState) {
    super.onSaveInstanceState(savedInstanceState);
    savedInstanceState.putString("passphrase", passphrase);
    savedInstanceState.putStringArray("entries", adapter.toStringArray());
  }

  private void getPassphrase() {
    final EditText pass = new EditText(this);
    pass.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);

    AlertDialog.Builder builder = new AlertDialog.Builder(this);

    builder.setMessage(getString(R.string.prompt));
    builder.setView(pass);
    builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
      public void onClick(DialogInterface dialog, int which) {
        passphrase = pass.getText().toString();
        if (passphrase.length() != 0) new ReadEntriesTask().execute(passphrase);
      }
    });

    builder.show();
  }

  private void updateEntries() {
    if (settings.getString("passwords", "").length() == 0 || settings.getString("key_file", "").length() == 0) {
      setEmptyText(R.string.settings);
    } else {
      setEmptyText(R.string.locked);
      if (passphrase.length() == 0) {
        getPassphrase();
      } else {
        new ReadEntriesTask().execute(passphrase);
      }
    }
  }

  private void setEmptyText(int stringResource) {
    ((TextView) findViewById(android.R.id.empty)).setText(stringResource);
  }

  private boolean isLocked() {
    return passphrase.length() == 0;
  }

  @SuppressWarnings("deprecation")
  private void copyPassword(Entry entry) {

    int sdk = Build.VERSION.SDK_INT;
    if (sdk < 11) {
      ((android.text.ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE)).setText(entry.password);
    } else {
      ClipData data = ClipData.newPlainText("simple text", entry.password);
      ((android.content.ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE)).setPrimaryClip(data);
    }

    Toast.makeText(Vecna.this, getString(R.string.copied, entry.account), Toast.LENGTH_SHORT).show();
  }

  private boolean populateStreamFromFactory(PGPObjectFactory factory, ByteArrayOutputStream stream) throws IOException, PGPException {
    Object message = factory.nextObject();;

    while (message != null) {
      if (message instanceof PGPCompressedData) {
        PGPObjectFactory subFactory = new PGPObjectFactory(((PGPCompressedData) message).getDataStream());
        if (populateStreamFromFactory(subFactory, stream)) {
          return true;
        }
      } else if (message instanceof PGPLiteralData) {
        InputStream s = ((PGPLiteralData) message).getInputStream();

        int ch;
        while ((ch = s.read()) >= 0) {
          stream.write(ch);
        }
        return true;
      } else {
        Log.d(TAG, "Unknown PGP object: " + message.getClass());
      }

      message = factory.nextObject();
    }

    return false;
  }
}




Java Source Code List

org.eatabrick.vecna.ActionBarListActivity.java
org.eatabrick.vecna.Entry.java
org.eatabrick.vecna.PasswordEntryAdapter.java
org.eatabrick.vecna.Preferences.java
org.eatabrick.vecna.Vecna.java