Refactor the database classes to be more reusable

This commit is contained in:
Alexander Bakker 2018-10-06 22:23:38 +02:00
parent 571cf20eda
commit 0434513820
18 changed files with 311 additions and 256 deletions

View file

@ -25,6 +25,7 @@ import me.impy.aegis.R;
import me.impy.aegis.crypto.KeyStoreHandle;
import me.impy.aegis.crypto.KeyStoreHandleException;
import me.impy.aegis.crypto.MasterKey;
import me.impy.aegis.db.DatabaseFileCredentials;
import me.impy.aegis.db.slots.FingerprintSlot;
import me.impy.aegis.db.slots.PasswordSlot;
import me.impy.aegis.db.slots.Slot;
@ -128,7 +129,7 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
private void setKey(MasterKey key) {
// send the master key back to the main activity
Intent result = new Intent();
result.putExtra("key", key);
result.putExtra("creds", new DatabaseFileCredentials(key, _slots));
setResult(RESULT_OK, result);
finish();
}

View file

@ -16,7 +16,7 @@ import javax.crypto.SecretKey;
import me.impy.aegis.Preferences;
import me.impy.aegis.R;
import me.impy.aegis.crypto.MasterKey;
import me.impy.aegis.db.DatabaseFileCredentials;
import me.impy.aegis.db.DatabaseFileException;
import me.impy.aegis.db.DatabaseManagerException;
import me.impy.aegis.db.slots.FingerprintSlot;
@ -128,9 +128,9 @@ public class IntroActivity extends AppIntro2 implements DerivationTask.Callback
}
// generate the master key
MasterKey masterKey = null;
DatabaseFileCredentials creds = null;
if (cryptType != CustomAuthenticationSlide.CRYPT_TYPE_NONE) {
masterKey = MasterKey.generate();
creds = new DatabaseFileCredentials();
}
SlotList slots = null;
@ -141,10 +141,8 @@ public class IntroActivity extends AppIntro2 implements DerivationTask.Callback
throw new RuntimeException();
}
try {
_passwordSlot.setKey(masterKey, _passwordCipher);
slots = new SlotList();
slots.add(_passwordSlot);
_databaseFile.setSlots(slots);
_passwordSlot.setKey(creds.getKey(), _passwordCipher);
creds.getSlots().add(_passwordSlot);
} catch (SlotException e) {
setException(e);
}
@ -156,8 +154,8 @@ public class IntroActivity extends AppIntro2 implements DerivationTask.Callback
// and add it to the list of slots
FingerprintSlot slot = _authenticatedSlide.getFingerSlot();
Cipher cipher = _authenticatedSlide.getFingerCipher();
slot.setKey(masterKey, cipher);
slots.add(slot);
slot.setKey(creds.getKey(), cipher);
creds.getSlots().add(slot);
} catch (SlotException e) {
setException(e);
return;
@ -166,11 +164,11 @@ public class IntroActivity extends AppIntro2 implements DerivationTask.Callback
// finally, save the database
try {
JSONObject obj = _database.serialize();
JSONObject obj = _database.toJson();
if (cryptType == CustomAuthenticationSlide.CRYPT_TYPE_NONE) {
_databaseFile.setContent(obj);
} else {
_databaseFile.setContent(obj, masterKey);
_databaseFile.setContent(obj, creds);
}
DatabaseManager.save(getApplicationContext(), _databaseFile);
} catch (DatabaseManagerException | DatabaseFileException e) {
@ -180,7 +178,7 @@ public class IntroActivity extends AppIntro2 implements DerivationTask.Callback
// send the master key back to the main activity
Intent result = new Intent();
result.putExtra("key", masterKey);
result.putExtra("creds", creds);
setResult(RESULT_OK, result);
// skip the intro from now on

View file

@ -20,7 +20,7 @@ import java.lang.reflect.UndeclaredThrowableException;
import me.impy.aegis.AegisApplication;
import me.impy.aegis.R;
import me.impy.aegis.crypto.MasterKey;
import me.impy.aegis.db.DatabaseFileCredentials;
import me.impy.aegis.db.DatabaseManagerException;
import me.impy.aegis.db.DatabaseEntry;
import me.impy.aegis.db.DatabaseManager;
@ -218,13 +218,13 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
throw new UndeclaredThrowableException(e);
}
MasterKey key = (MasterKey) data.getSerializableExtra("key");
unlockDatabase(key);
DatabaseFileCredentials creds = (DatabaseFileCredentials) data.getSerializableExtra("creds");
unlockDatabase(creds);
}
private void onDecryptResult(int resultCode, Intent intent) {
MasterKey key = (MasterKey) intent.getSerializableExtra("key");
unlockDatabase(key);
DatabaseFileCredentials creds = (DatabaseFileCredentials) intent.getSerializableExtra("creds");
unlockDatabase(creds);
doShortcutActions();
}
@ -361,7 +361,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
}
}
private void unlockDatabase(MasterKey key) {
private void unlockDatabase(DatabaseFileCredentials creds) {
if (_loaded) {
return;
}
@ -371,15 +371,14 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
_db.load();
}
if (_db.isLocked()) {
if (key == null) {
if (creds == null) {
startAuthActivity();
return;
} else {
_db.unlock(key);
_db.unlock(creds);
}
}
} catch (DatabaseManagerException e) {
e.printStackTrace();
Toast.makeText(this, "An error occurred while trying to load/decrypt the database", Toast.LENGTH_LONG).show();
startAuthActivity();
return;
@ -396,7 +395,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
private void startAuthActivity() {
Intent intent = new Intent(this, AuthActivity.class);
intent.putExtra("slots", _db.getFile().getSlots());
intent.putExtra("slots", _db.getFileHeader().getSlots());
startActivityForResult(intent, CODE_DECRYPT);
}
@ -404,7 +403,6 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
try {
_db.save();
} catch (DatabaseManagerException e) {
e.printStackTrace();
Toast.makeText(this, "An error occurred while trying to save the database", Toast.LENGTH_LONG).show();
}
}
@ -413,7 +411,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
// hide the lock icon if the database is not unlocked
if (_menu != null && !_db.isLocked()) {
MenuItem item = _menu.findItem(R.id.action_lock);
item.setVisible(_db.getFile().isEncrypted());
item.setVisible(_db.isEncryptionEnabled());
}
}

View file

@ -26,12 +26,12 @@ import javax.crypto.Cipher;
import me.impy.aegis.AegisApplication;
import me.impy.aegis.R;
import me.impy.aegis.crypto.MasterKey;
import me.impy.aegis.db.DatabaseEntry;
import me.impy.aegis.db.DatabaseFileCredentials;
import me.impy.aegis.db.DatabaseFileException;
import me.impy.aegis.db.DatabaseManager;
import me.impy.aegis.db.DatabaseManagerException;
import me.impy.aegis.db.slots.Slot;
import me.impy.aegis.db.slots.SlotList;
import me.impy.aegis.db.slots.SlotException;
import me.impy.aegis.helpers.PermissionHelper;
import me.impy.aegis.importers.AegisImporter;
@ -138,7 +138,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
_encryptionPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (!_db.getFile().isEncrypted()) {
if (!_db.isEncryptionEnabled()) {
PasswordDialogFragment dialog = new PasswordDialogFragment();
// TODO: find a less ugly way to obtain the fragment manager
dialog.show(getActivity().getSupportFragmentManager(), null);
@ -148,8 +148,11 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
.setMessage("Are you sure you want to disable encryption? This will cause the database to be stored in plain text")
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
_db.disableEncryption();
saveDatabase();
try {
_db.disableEncryption();
} catch (DatabaseManagerException e) {
Toast.makeText(getActivity(), "An error occurred while enabling encryption", Toast.LENGTH_SHORT).show();
}
updateEncryptionPreference();
}
})
@ -163,10 +166,8 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
_slotsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
MasterKey masterKey = _db.getMasterKey();
Intent intent = new Intent(getActivity(), SlotManagerActivity.class);
intent.putExtra("masterKey", masterKey);
intent.putExtra("slots", _db.getFile().getSlots());
intent.putExtra("creds", _db.getCredentials());
startActivityForResult(intent, CODE_SLOTS);
return true;
}
@ -249,13 +250,12 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
return;
}
MasterKey key = (MasterKey) data.getSerializableExtra("key");
((AegisImporter)_importer).setKey(key);
DatabaseFileCredentials creds = (DatabaseFileCredentials) data.getSerializableExtra("creds");
((AegisImporter)_importer).setCredentials(creds);
try {
importDatabase(_importer);
} catch (DatabaseImporterException e) {
e.printStackTrace();
Toast.makeText(getActivity(), "An error occurred while trying to parse the file", Toast.LENGTH_SHORT).show();
}
@ -269,25 +269,16 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
}
ByteInputStream stream;
InputStream fileStream = null;
try {
fileStream = getActivity().getContentResolver().openInputStream(uri);
try (InputStream fileStream = getActivity().getContentResolver().openInputStream(uri)) {
stream = ByteInputStream.create(fileStream);
} catch (FileNotFoundException e) {
Toast.makeText(getActivity(), "Error: File not found", Toast.LENGTH_SHORT).show();
return;
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(getActivity(), "An error occurred while trying to read the file", Toast.LENGTH_SHORT).show();
return;
} finally {
if (fileStream != null) {
try {
fileStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
try {
@ -299,7 +290,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
_importer = importer;
Intent intent = new Intent(getActivity(), AuthActivity.class);
intent.putExtra("slots", ((AegisImporter)_importer).getFile().getSlots());
intent.putExtra("slots", ((AegisImporter)_importer).getFile().getHeader().getSlots());
startActivityForResult(intent, CODE_IMPORT_DECRYPT);
return;
}
@ -344,7 +335,6 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
try {
filename = _db.export(checked[0]);
} catch (DatabaseManagerException e) {
e.printStackTrace();
Toast.makeText(getActivity(), "An error occurred while trying to export the database", Toast.LENGTH_SHORT).show();
return;
}
@ -355,7 +345,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
Toast.makeText(getActivity(), "The database has been exported to: " + filename, Toast.LENGTH_SHORT).show();
})
.setNegativeButton(android.R.string.cancel, null);
if (_db.getFile().isEncrypted()) {
if (_db.isEncryptionEnabled()) {
final String[] items = {"Keep the database encrypted"};
final boolean[] checkedItems = {true};
builder.setMultiChoiceItems(items, checkedItems, new DialogInterface.OnMultiChoiceClickListener() {
@ -375,8 +365,8 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
return;
}
SlotList slots = (SlotList) data.getSerializableExtra("slots");
_db.getFile().setSlots(slots);
DatabaseFileCredentials creds = (DatabaseFileCredentials) data.getSerializableExtra("creds");
_db.setCredentials(creds);
saveDatabase();
}
@ -384,7 +374,6 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
try {
_db.save();
} catch (DatabaseManagerException e) {
e.printStackTrace();
Toast.makeText(getActivity(), "An error occurred while trying to save the database", Toast.LENGTH_LONG).show();
return false;
}
@ -394,19 +383,17 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
@Override
public void onSlotResult(Slot slot, Cipher cipher) {
MasterKey masterKey = MasterKey.generate();
DatabaseFileCredentials creds = new DatabaseFileCredentials();
SlotList slots = new SlotList();
try {
slot.setKey(masterKey, cipher);
} catch (SlotException e) {
slot.setKey(creds.getKey(), cipher);
creds.getSlots().add(slot);
_db.enableEncryption(creds);
} catch (DatabaseManagerException | SlotException e) {
onException(e);
return;
}
slots.add(slot);
_db.enableEncryption(masterKey, slots);
saveDatabase();
updateEncryptionPreference();
}
@ -417,7 +404,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
}
private void updateEncryptionPreference() {
boolean encrypted = _db.getFile().isEncrypted();
boolean encrypted = _db.isEncryptionEnabled();
_encryptionPreference.setChecked(encrypted, true);
_slotsPreference.setEnabled(encrypted);
}

View file

@ -110,10 +110,7 @@ public class ScannerActivity extends AegisActivity implements ZXingScannerView.R
// parse google auth uri
Uri uri = Uri.parse(rawResult.getText());
GoogleAuthInfo info = GoogleAuthInfo.parseUri(uri);
DatabaseEntry entry = new DatabaseEntry(info.getOtpInfo());
entry.setIssuer(info.getIssuer());
entry.setName(info.getAccountName());
DatabaseEntry entry = new DatabaseEntry(info);
Intent intent = new Intent();
intent.putExtra("entry", entry);

View file

@ -16,7 +16,7 @@ import javax.crypto.Cipher;
import me.impy.aegis.R;
import me.impy.aegis.crypto.KeyStoreHandle;
import me.impy.aegis.crypto.KeyStoreHandleException;
import me.impy.aegis.crypto.MasterKey;
import me.impy.aegis.db.DatabaseFileCredentials;
import me.impy.aegis.db.slots.FingerprintSlot;
import me.impy.aegis.db.slots.PasswordSlot;
import me.impy.aegis.db.slots.Slot;
@ -29,8 +29,7 @@ import me.impy.aegis.ui.views.SlotAdapter;
import me.impy.aegis.ui.dialogs.SlotDialogFragment;
public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Listener, SlotDialogFragment.Listener {
private MasterKey _masterKey;
private SlotList _slots;
private DatabaseFileCredentials _creds;
private SlotAdapter _adapter;
private boolean _edited;
@ -59,9 +58,8 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
slotsView.setNestedScrollingEnabled(false);
// load the slots and masterKey
_masterKey = (MasterKey) getIntent().getSerializableExtra("masterKey");
_slots = (SlotList) getIntent().getSerializableExtra("slots");
for (Slot slot : _slots) {
_creds = (DatabaseFileCredentials) getIntent().getSerializableExtra("creds");
for (Slot slot : _creds.getSlots()) {
_adapter.addSlot(slot);
}
@ -75,7 +73,7 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
if (FingerprintHelper.getManager(this) != null) {
try {
KeyStoreHandle keyStore = new KeyStoreHandle();
for (FingerprintSlot slot : _slots.findAll(FingerprintSlot.class)) {
for (FingerprintSlot slot : _creds.getSlots().findAll(FingerprintSlot.class)) {
if (keyStore.containsKey(slot.getUUID().toString())) {
visibility = View.GONE;
break;
@ -92,7 +90,7 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
private void onSave() {
Intent intent = new Intent();
intent.putExtra("slots", _slots);
intent.putExtra("creds", _creds);
setResult(RESULT_OK, intent);
finish();
}
@ -150,7 +148,8 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
@Override
public void onRemoveSlot(Slot slot) {
if (slot instanceof PasswordSlot && _slots.findAll(PasswordSlot.class).size() <= 1) {
SlotList slots = _creds.getSlots();
if (slot instanceof PasswordSlot && slots.findAll(PasswordSlot.class).size() <= 1) {
Toast.makeText(this, "You must have at least one password slot", Toast.LENGTH_SHORT).show();
return;
}
@ -159,7 +158,7 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
.setTitle("Remove slot")
.setMessage("Are you sure you want to remove this slot?")
.setPositiveButton(android.R.string.yes, (dialog, whichButton) -> {
_slots.remove(slot);
slots.remove(slot);
_adapter.removeSlot(slot);
_edited = true;
updateFingerprintButton();
@ -171,13 +170,13 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
@Override
public void onSlotResult(Slot slot, Cipher cipher) {
try {
slot.setKey(_masterKey, cipher);
slot.setKey(_creds.getKey(), cipher);
} catch (SlotException e) {
onException(e);
return;
}
_slots.add(slot);
_creds.getSlots().add(slot);
_adapter.addSlot(slot);
_edited = true;
updateFingerprintButton();