diff --git a/app/src/main/java/me/impy/aegis/db/DatabaseFile.java b/app/src/main/java/me/impy/aegis/db/DatabaseFile.java index aa60e422..8f9d216a 100644 --- a/app/src/main/java/me/impy/aegis/db/DatabaseFile.java +++ b/app/src/main/java/me/impy/aegis/db/DatabaseFile.java @@ -23,21 +23,17 @@ public class DatabaseFile { private CryptParameters _cryptParameters; private SlotCollection _slots; - public DatabaseFile() { - _slots = new SlotCollection(); - } - public byte[] serialize() throws DatabaseFileException { try { JSONObject cryptObj = null; - if (_cryptParameters != null) { + if (isEncrypted()) { cryptObj = new JSONObject(); cryptObj.put("nonce", Hex.encode(_cryptParameters.Nonce)); cryptObj.put("tag", Hex.encode(_cryptParameters.Tag)); } // don't write the crypt parameters if the content is not encrypted - boolean plain = _content instanceof JSONObject || _slots.isEmpty() || cryptObj == null; + boolean plain = _content instanceof JSONObject || _slots == null || cryptObj == null; JSONObject headerObj = new JSONObject(); headerObj.put("slots", plain ? JSONObject.NULL : SlotCollection.serialize(_slots)); headerObj.put("params", plain ? JSONObject.NULL : cryptObj); @@ -86,7 +82,7 @@ public class DatabaseFile { } public boolean isEncrypted() { - return !_slots.isEmpty() && _cryptParameters != null; + return _slots != null; } public JSONObject getContent() { @@ -106,6 +102,7 @@ public class DatabaseFile { public void setContent(JSONObject dbObj) { _content = dbObj; _cryptParameters = null; + _slots = null; } public void setContent(JSONObject dbObj, MasterKey key) throws DatabaseFileException { diff --git a/app/src/main/java/me/impy/aegis/db/DatabaseManager.java b/app/src/main/java/me/impy/aegis/db/DatabaseManager.java index 28e8ac1c..a50f92dd 100644 --- a/app/src/main/java/me/impy/aegis/db/DatabaseManager.java +++ b/app/src/main/java/me/impy/aegis/db/DatabaseManager.java @@ -14,6 +14,7 @@ import java.util.List; import me.impy.aegis.BuildConfig; import me.impy.aegis.crypto.MasterKey; +import me.impy.aegis.db.slots.SlotCollection; public class DatabaseManager { private static final String FILENAME = "aegis.json"; @@ -195,6 +196,18 @@ public class DatabaseManager { return _file; } + public void enableEncryption(MasterKey key, SlotCollection slots) { + assertState(false, true); + _key = key; + _file.setSlots(slots); + } + + public void disableEncryption() { + assertState(false, true); + _key = null; + _file.setSlots(null); + } + public boolean isLoaded() { return _file != null; } diff --git a/app/src/main/java/me/impy/aegis/db/slots/SlotCollection.java b/app/src/main/java/me/impy/aegis/db/slots/SlotCollection.java index d5d2862a..e9ae476e 100644 --- a/app/src/main/java/me/impy/aegis/db/slots/SlotCollection.java +++ b/app/src/main/java/me/impy/aegis/db/slots/SlotCollection.java @@ -89,10 +89,6 @@ public class SlotCollection implements Iterable, Serializable { return _slots.size(); } - public boolean isEmpty() { - return _slots.size() == 0; - } - public T find(Class type) { for (Slot slot : this) { if (slot.getClass() == type) { diff --git a/app/src/main/java/me/impy/aegis/ui/MainActivity.java b/app/src/main/java/me/impy/aegis/ui/MainActivity.java index 66be160e..4c603dc0 100644 --- a/app/src/main/java/me/impy/aegis/ui/MainActivity.java +++ b/app/src/main/java/me/impy/aegis/ui/MainActivity.java @@ -327,6 +327,7 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen @Override protected void onResume() { super.onResume(); + updateLockIcon(); // refresh all codes to prevent showing old ones _keyProfileView.refresh(); diff --git a/app/src/main/java/me/impy/aegis/ui/PreferencesActivity.java b/app/src/main/java/me/impy/aegis/ui/PreferencesActivity.java index 322795c2..1ac53eef 100644 --- a/app/src/main/java/me/impy/aegis/ui/PreferencesActivity.java +++ b/app/src/main/java/me/impy/aegis/ui/PreferencesActivity.java @@ -2,7 +2,12 @@ package me.impy.aegis.ui; import android.os.Bundle; -public class PreferencesActivity extends AegisActivity { +import javax.crypto.Cipher; + +import me.impy.aegis.db.slots.Slot; +import me.impy.aegis.ui.dialogs.PasswordDialogFragment; + +public class PreferencesActivity extends AegisActivity implements PasswordDialogFragment.Listener { private PreferencesFragment _fragment; @Override @@ -36,4 +41,14 @@ public class PreferencesActivity extends AegisActivity { outState.putParcelable("result", _fragment.getResult()); super.onSaveInstanceState(outState); } + + @Override + public void onSlotResult(Slot slot, Cipher cipher) { + _fragment.onSlotResult(slot, cipher); + } + + @Override + public void onException(Exception e) { + _fragment.onException(e); + } } diff --git a/app/src/main/java/me/impy/aegis/ui/PreferencesFragment.java b/app/src/main/java/me/impy/aegis/ui/PreferencesFragment.java index 2a8fdc64..ac68ca00 100644 --- a/app/src/main/java/me/impy/aegis/ui/PreferencesFragment.java +++ b/app/src/main/java/me/impy/aegis/ui/PreferencesFragment.java @@ -10,7 +10,9 @@ import android.os.Bundle; import android.preference.EditTextPreference; import android.preference.Preference; import android.preference.PreferenceFragment; +import android.preference.SwitchPreference; import android.support.v7.app.AlertDialog; +import android.support.v7.app.AppCompatActivity; import android.view.Window; import android.view.WindowManager; import android.widget.Toast; @@ -22,20 +24,25 @@ import java.util.List; import java.util.Locale; import java.util.Map; +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.DatabaseManager; import me.impy.aegis.db.DatabaseManagerException; +import me.impy.aegis.db.slots.Slot; import me.impy.aegis.db.slots.SlotCollection; +import me.impy.aegis.db.slots.SlotException; import me.impy.aegis.helpers.PermissionHelper; import me.impy.aegis.importers.AegisImporter; import me.impy.aegis.importers.DatabaseImporter; import me.impy.aegis.importers.DatabaseImporterException; +import me.impy.aegis.ui.dialogs.PasswordDialogFragment; import me.impy.aegis.util.ByteInputStream; -public class PreferencesFragment extends PreferenceFragment { +public class PreferencesFragment extends PreferenceFragment implements PasswordDialogFragment.Listener { // activity request codes private static final int CODE_IMPORT = 0; private static final int CODE_IMPORT_DECRYPT = 1; @@ -53,6 +60,9 @@ public class PreferencesFragment extends PreferenceFragment { private DatabaseImporter _importer; private Class _importerType; + private SwitchPreference _encryptionPreference; + private Preference _slotsPreference; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -92,20 +102,6 @@ public class PreferencesFragment extends PreferenceFragment { } }); - Preference slotsPreference = findPreference("pref_slots"); - slotsPreference.setEnabled(_db.getFile().isEncrypted()); - 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()); - startActivityForResult(intent, CODE_SLOTS); - return true; - } - }); - EditTextPreference timeoutPreference = (EditTextPreference) findPreference("pref_timeout"); timeoutPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @Override @@ -139,6 +135,45 @@ public class PreferencesFragment extends PreferenceFragment { return true; } }); + + _encryptionPreference = (SwitchPreference) findPreference("pref_encryption"); + _encryptionPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (!_db.getFile().isEncrypted()) { + PasswordDialogFragment dialog = new PasswordDialogFragment(); + // TODO: find a less ugly way to obtain the fragment manager + dialog.show(((AppCompatActivity)getActivity()).getSupportFragmentManager(), null); + } else { + new AlertDialog.Builder(getActivity()) + .setTitle("Disable encryption") + .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(); + updateEncryptionPreference(); + } + }) + .setNegativeButton(android.R.string.no, null) + .show(); + } + return false; + } + }); + _slotsPreference = findPreference("pref_slots"); + _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()); + startActivityForResult(intent, CODE_SLOTS); + return true; + } + }); + updateEncryptionPreference(); } @Override @@ -195,8 +230,6 @@ public class PreferencesFragment extends PreferenceFragment { .setSingleChoiceItems(names, 0, null) .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition(); _importerType = importers.get(names[i]); @@ -353,4 +386,34 @@ public class PreferencesFragment extends PreferenceFragment { return true; } + + @Override + public void onSlotResult(Slot slot, Cipher cipher) { + MasterKey masterKey = MasterKey.generate(); + + SlotCollection slots = new SlotCollection(); + try { + slots.encrypt(slot, masterKey, cipher); + } catch (SlotException e) { + onException(e); + return; + } + slots.add(slot); + _db.enableEncryption(masterKey, slots); + + saveDatabase(); + updateEncryptionPreference(); + } + + @Override + public void onException(Exception e) { + updateEncryptionPreference(); + Toast.makeText(getActivity(), "An error occurred while trying to set the password: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + } + + private void updateEncryptionPreference() { + boolean encrypted = _db.getFile().isEncrypted(); + _encryptionPreference.setChecked(encrypted); + _slotsPreference.setEnabled(encrypted); + } } diff --git a/app/src/main/java/me/impy/aegis/ui/dialogs/FingerprintDialogFragment.java b/app/src/main/java/me/impy/aegis/ui/dialogs/FingerprintDialogFragment.java index c2081d1d..c34a2cf9 100644 --- a/app/src/main/java/me/impy/aegis/ui/dialogs/FingerprintDialogFragment.java +++ b/app/src/main/java/me/impy/aegis/ui/dialogs/FingerprintDialogFragment.java @@ -3,8 +3,8 @@ package me.impy.aegis.ui.dialogs; import android.app.Dialog; import android.hardware.fingerprint.FingerprintManager; import android.os.Bundle; -import android.support.annotation.NonNull; import android.support.v7.app.AlertDialog; +import android.support.annotation.NonNull; import android.view.View; import android.widget.TextView; diff --git a/app/src/main/java/me/impy/aegis/ui/dialogs/PasswordDialogFragment.java b/app/src/main/java/me/impy/aegis/ui/dialogs/PasswordDialogFragment.java index 65b1e8d4..3f46a1e6 100644 --- a/app/src/main/java/me/impy/aegis/ui/dialogs/PasswordDialogFragment.java +++ b/app/src/main/java/me/impy/aegis/ui/dialogs/PasswordDialogFragment.java @@ -48,7 +48,7 @@ public class PasswordDialogFragment extends SlotDialogFragment { char[] password = EditTextHelper.getEditTextChars(textPassword); PasswordSlot slot = new PasswordSlot(); - DerivationTask task = new DerivationTask(getContext(), key -> { + DerivationTask task = new DerivationTask(getActivity(), key -> { Cipher cipher; try { cipher = Slot.createCipher(key, Cipher.ENCRYPT_MODE); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index dd497b1a..8332990e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -22,6 +22,8 @@ Export the database Screen security Block screenshots and other attempts to capture the screen within the app + Encryption + Encrypt the database and unlock it with a password or fingerprint Touch sensor Fingerprint not recognized. Try again diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index ca1457d3..0dd28003 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -31,6 +31,11 @@ android:inputType="number" android:defaultValue="30" android:dialogTitle="Set number of seconds of inactivity before Aegis locks the database"/> +