mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-06-22 17:10:56 +00:00
Make fingerprint unlock toggleable
Also: - Fix a bug where setting the password would clear the key slot list - Show the "Key slots" preference if multiple slots of one kind exist
This commit is contained in:
parent
926b5139da
commit
80b1967693
10 changed files with 317 additions and 261 deletions
|
@ -2,12 +2,7 @@ package me.impy.aegis.ui;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
public class PreferencesActivity extends AegisActivity {
|
||||||
|
|
||||||
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;
|
private PreferencesFragment _fragment;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -41,14 +36,4 @@ public class PreferencesActivity extends AegisActivity implements PasswordDialog
|
||||||
outState.putParcelable("result", _fragment.getResult());
|
outState.putParcelable("result", _fragment.getResult());
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSlotResult(Slot slot, Cipher cipher) {
|
|
||||||
_fragment.onSlotResult(slot, cipher);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onException(Exception e) {
|
|
||||||
_fragment.onException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,20 +29,23 @@ import me.impy.aegis.BuildConfig;
|
||||||
import me.impy.aegis.R;
|
import me.impy.aegis.R;
|
||||||
import me.impy.aegis.db.DatabaseEntry;
|
import me.impy.aegis.db.DatabaseEntry;
|
||||||
import me.impy.aegis.db.DatabaseFileCredentials;
|
import me.impy.aegis.db.DatabaseFileCredentials;
|
||||||
import me.impy.aegis.db.DatabaseFileException;
|
|
||||||
import me.impy.aegis.db.DatabaseManager;
|
import me.impy.aegis.db.DatabaseManager;
|
||||||
import me.impy.aegis.db.DatabaseManagerException;
|
import me.impy.aegis.db.DatabaseManagerException;
|
||||||
|
import me.impy.aegis.db.slots.FingerprintSlot;
|
||||||
|
import me.impy.aegis.db.slots.PasswordSlot;
|
||||||
import me.impy.aegis.db.slots.Slot;
|
import me.impy.aegis.db.slots.Slot;
|
||||||
import me.impy.aegis.db.slots.SlotException;
|
import me.impy.aegis.db.slots.SlotException;
|
||||||
|
import me.impy.aegis.db.slots.SlotList;
|
||||||
|
import me.impy.aegis.helpers.FingerprintHelper;
|
||||||
import me.impy.aegis.helpers.PermissionHelper;
|
import me.impy.aegis.helpers.PermissionHelper;
|
||||||
import me.impy.aegis.importers.AegisImporter;
|
import me.impy.aegis.importers.AegisImporter;
|
||||||
import me.impy.aegis.importers.DatabaseImporter;
|
import me.impy.aegis.importers.DatabaseImporter;
|
||||||
import me.impy.aegis.importers.DatabaseImporterException;
|
import me.impy.aegis.importers.DatabaseImporterException;
|
||||||
import me.impy.aegis.ui.dialogs.PasswordDialogFragment;
|
import me.impy.aegis.ui.dialogs.Dialogs;
|
||||||
import me.impy.aegis.ui.preferences.SwitchPreference;
|
import me.impy.aegis.ui.preferences.SwitchPreference;
|
||||||
import me.impy.aegis.util.ByteInputStream;
|
import me.impy.aegis.util.ByteInputStream;
|
||||||
|
|
||||||
public class PreferencesFragment extends PreferenceFragmentCompat implements PasswordDialogFragment.Listener {
|
public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||||
// activity request codes
|
// activity request codes
|
||||||
private static final int CODE_IMPORT = 0;
|
private static final int CODE_IMPORT = 0;
|
||||||
private static final int CODE_IMPORT_DECRYPT = 1;
|
private static final int CODE_IMPORT_DECRYPT = 1;
|
||||||
|
@ -61,6 +64,8 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
|
||||||
private Class<? extends DatabaseImporter> _importerType;
|
private Class<? extends DatabaseImporter> _importerType;
|
||||||
|
|
||||||
private SwitchPreference _encryptionPreference;
|
private SwitchPreference _encryptionPreference;
|
||||||
|
private SwitchPreference _fingerprintPreference;
|
||||||
|
private Preference _setPasswordPreference;
|
||||||
private Preference _slotsPreference;
|
private Preference _slotsPreference;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -140,9 +145,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||||
if (!_db.isEncryptionEnabled()) {
|
if (!_db.isEncryptionEnabled()) {
|
||||||
PasswordDialogFragment dialog = new PasswordDialogFragment();
|
Dialogs.showSetPasswordDialog(getActivity(), new EnableEncryptionListener());
|
||||||
// TODO: find a less ugly way to obtain the fragment manager
|
|
||||||
dialog.show(getActivity().getSupportFragmentManager(), null);
|
|
||||||
} else {
|
} else {
|
||||||
new AlertDialog.Builder(getActivity())
|
new AlertDialog.Builder(getActivity())
|
||||||
.setTitle(getString(R.string.disable_encryption))
|
.setTitle(getString(R.string.disable_encryption))
|
||||||
|
@ -154,7 +157,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
|
||||||
} catch (DatabaseManagerException e) {
|
} catch (DatabaseManagerException e) {
|
||||||
Toast.makeText(getActivity(), getString(R.string.encrypting_error), Toast.LENGTH_SHORT).show();
|
Toast.makeText(getActivity(), getString(R.string.encrypting_error), Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
updateEncryptionPreference();
|
updateEncryptionPreferences();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.setNegativeButton(android.R.string.no, null)
|
.setNegativeButton(android.R.string.no, null)
|
||||||
|
@ -164,20 +167,37 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Preference setPasswordPreference = findPreference("pref_password");
|
_fingerprintPreference = (SwitchPreference) findPreference("pref_fingerprint");
|
||||||
setPasswordPreference.setOnPreferenceClickListener(preference -> {
|
_fingerprintPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||||
PasswordDialogFragment dialog = new PasswordDialogFragment();
|
@Override
|
||||||
// TODO: find a less ugly way to obtain the fragment manager
|
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||||
dialog.show(getActivity().getSupportFragmentManager(), null);
|
DatabaseFileCredentials creds = _db.getCredentials();
|
||||||
|
SlotList slots = creds.getSlots();
|
||||||
|
|
||||||
|
if (!slots.has(FingerprintSlot.class)) {
|
||||||
|
Dialogs.showFingerprintDialog(getActivity(), new RegisterFingerprintListener());
|
||||||
|
} else {
|
||||||
|
// remove the fingerprint slot
|
||||||
|
FingerprintSlot slot = slots.find(FingerprintSlot.class);
|
||||||
|
slots.remove(slot);
|
||||||
|
_db.setCredentials(creds);
|
||||||
|
|
||||||
|
saveDatabase();
|
||||||
|
updateEncryptionPreferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_setPasswordPreference = findPreference("pref_password");
|
||||||
|
_setPasswordPreference.setOnPreferenceClickListener(preference -> {
|
||||||
|
Dialogs.showSetPasswordDialog(getActivity(), new SetPasswordListener());
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
_slotsPreference = findPreference("pref_slots");
|
_slotsPreference = findPreference("pref_slots");
|
||||||
if (BuildConfig.DEBUG) {
|
|
||||||
_slotsPreference.setVisible(true);
|
|
||||||
} else {
|
|
||||||
_slotsPreference.setVisible(false);
|
|
||||||
}
|
|
||||||
_slotsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
_slotsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreferenceClick(Preference preference) {
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
|
@ -187,7 +207,8 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
updateEncryptionPreference();
|
|
||||||
|
updateEncryptionPreferences();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -383,6 +404,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
|
||||||
DatabaseFileCredentials creds = (DatabaseFileCredentials) data.getSerializableExtra("creds");
|
DatabaseFileCredentials creds = (DatabaseFileCredentials) data.getSerializableExtra("creds");
|
||||||
_db.setCredentials(creds);
|
_db.setCredentials(creds);
|
||||||
saveDatabase();
|
saveDatabase();
|
||||||
|
updateEncryptionPreferences();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean saveDatabase() {
|
private boolean saveDatabase() {
|
||||||
|
@ -396,31 +418,108 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void updateEncryptionPreferences() {
|
||||||
public void onSlotResult(Slot slot, Cipher cipher) {
|
|
||||||
DatabaseFileCredentials creds = new DatabaseFileCredentials();
|
|
||||||
|
|
||||||
try {
|
|
||||||
slot.setKey(creds.getKey(), cipher);
|
|
||||||
creds.getSlots().add(slot);
|
|
||||||
_db.enableEncryption(creds);
|
|
||||||
} catch (DatabaseManagerException | SlotException e) {
|
|
||||||
onException(e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateEncryptionPreference();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onException(Exception e) {
|
|
||||||
updateEncryptionPreference();
|
|
||||||
Toast.makeText(getActivity(), getString(R.string.encryption_set_password_error) + e.getMessage(), Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateEncryptionPreference() {
|
|
||||||
boolean encrypted = _db.isEncryptionEnabled();
|
boolean encrypted = _db.isEncryptionEnabled();
|
||||||
_encryptionPreference.setChecked(encrypted, true);
|
_encryptionPreference.setChecked(encrypted, true);
|
||||||
|
_setPasswordPreference.setVisible(encrypted);
|
||||||
|
_fingerprintPreference.setVisible(encrypted);
|
||||||
_slotsPreference.setEnabled(encrypted);
|
_slotsPreference.setEnabled(encrypted);
|
||||||
|
|
||||||
|
if (encrypted) {
|
||||||
|
SlotList slots = _db.getCredentials().getSlots();
|
||||||
|
boolean multiPassword = slots.findAll(PasswordSlot.class).size() > 1;
|
||||||
|
boolean multiFinger = slots.findAll(FingerprintSlot.class).size() > 1;
|
||||||
|
boolean showSlots = BuildConfig.DEBUG || multiPassword || multiFinger;
|
||||||
|
_setPasswordPreference.setEnabled(!multiPassword);
|
||||||
|
_fingerprintPreference.setEnabled(FingerprintHelper.getManager(getContext()) != null && !multiFinger);
|
||||||
|
_fingerprintPreference.setChecked(slots.has(FingerprintSlot.class), true);
|
||||||
|
_slotsPreference.setVisible(showSlots);
|
||||||
|
} else {
|
||||||
|
_setPasswordPreference.setEnabled(false);
|
||||||
|
_fingerprintPreference.setEnabled(false);
|
||||||
|
_fingerprintPreference.setChecked(false, true);
|
||||||
|
_slotsPreference.setVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SetPasswordListener implements Dialogs.SlotListener {
|
||||||
|
@Override
|
||||||
|
public void onSlotResult(Slot slot, Cipher cipher) {
|
||||||
|
DatabaseFileCredentials creds = new DatabaseFileCredentials();
|
||||||
|
|
||||||
|
try {
|
||||||
|
slot.setKey(creds.getKey(), cipher);
|
||||||
|
creds.getSlots().add(slot);
|
||||||
|
_db.enableEncryption(creds);
|
||||||
|
} catch (DatabaseManagerException | SlotException e) {
|
||||||
|
onException(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateEncryptionPreferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException(Exception e) {
|
||||||
|
updateEncryptionPreferences();
|
||||||
|
Toast.makeText(getActivity(), getString(R.string.encryption_set_password_error) + e.getMessage(), Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RegisterFingerprintListener implements Dialogs.SlotListener {
|
||||||
|
@Override
|
||||||
|
public void onSlotResult(Slot slot, Cipher cipher) {
|
||||||
|
DatabaseFileCredentials creds = _db.getCredentials();
|
||||||
|
SlotList slots = creds.getSlots();
|
||||||
|
|
||||||
|
try {
|
||||||
|
slot.setKey(creds.getKey(), cipher);
|
||||||
|
} catch (SlotException e) {
|
||||||
|
onException(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
slots.add(slot);
|
||||||
|
_db.setCredentials(creds);
|
||||||
|
|
||||||
|
saveDatabase();
|
||||||
|
updateEncryptionPreferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException(Exception e) {
|
||||||
|
Toast.makeText(getActivity(), getString(R.string.encryption_enable_fingerprint_error) + e.getMessage(), Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class EnableEncryptionListener implements Dialogs.SlotListener {
|
||||||
|
@Override
|
||||||
|
public void onSlotResult(Slot slot, Cipher cipher) {
|
||||||
|
DatabaseFileCredentials creds = _db.getCredentials();
|
||||||
|
SlotList slots = creds.getSlots();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// encrypt the master key for this slot
|
||||||
|
slot.setKey(creds.getKey(), cipher);
|
||||||
|
|
||||||
|
// remove the old master password slot
|
||||||
|
PasswordSlot oldSlot = creds.getSlots().find(PasswordSlot.class);
|
||||||
|
slots.remove(oldSlot);
|
||||||
|
|
||||||
|
// add the new master password slot
|
||||||
|
slots.add(slot);
|
||||||
|
} catch (SlotException e) {
|
||||||
|
onException(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_db.setCredentials(creds);
|
||||||
|
saveDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException(Exception e) {
|
||||||
|
Toast.makeText(getActivity(), getString(R.string.encryption_set_password_error) + e.getMessage(), Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,11 +24,9 @@ import me.impy.aegis.db.slots.SlotList;
|
||||||
import me.impy.aegis.db.slots.SlotException;
|
import me.impy.aegis.db.slots.SlotException;
|
||||||
import me.impy.aegis.helpers.FingerprintHelper;
|
import me.impy.aegis.helpers.FingerprintHelper;
|
||||||
import me.impy.aegis.ui.dialogs.Dialogs;
|
import me.impy.aegis.ui.dialogs.Dialogs;
|
||||||
import me.impy.aegis.ui.dialogs.FingerprintDialogFragment;
|
|
||||||
import me.impy.aegis.ui.views.SlotAdapter;
|
import me.impy.aegis.ui.views.SlotAdapter;
|
||||||
import me.impy.aegis.ui.dialogs.SlotDialogFragment;
|
|
||||||
|
|
||||||
public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Listener, SlotDialogFragment.Listener {
|
public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Listener, Dialogs.SlotListener {
|
||||||
private DatabaseFileCredentials _creds;
|
private DatabaseFileCredentials _creds;
|
||||||
private SlotAdapter _adapter;
|
private SlotAdapter _adapter;
|
||||||
|
|
||||||
|
@ -45,8 +43,11 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
|
||||||
bar.setDisplayHomeAsUpEnabled(true);
|
bar.setDisplayHomeAsUpEnabled(true);
|
||||||
|
|
||||||
findViewById(R.id.button_add_fingerprint).setOnClickListener(view -> {
|
findViewById(R.id.button_add_fingerprint).setOnClickListener(view -> {
|
||||||
FingerprintDialogFragment dialog = new FingerprintDialogFragment();
|
Dialogs.showFingerprintDialog(this ,this);
|
||||||
dialog.show(getSupportFragmentManager(), null);
|
});
|
||||||
|
|
||||||
|
findViewById(R.id.button_add_password).setOnClickListener(view -> {
|
||||||
|
Dialogs.showSetPasswordDialog(this, this);
|
||||||
});
|
});
|
||||||
|
|
||||||
// set up the recycler view
|
// set up the recycler view
|
||||||
|
|
|
@ -2,17 +2,40 @@ package me.impy.aegis.ui.dialogs;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
|
import android.hardware.fingerprint.FingerprintManager;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.TextWatcher;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.mattprecious.swirl.SwirlView;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
import me.impy.aegis.R;
|
import me.impy.aegis.R;
|
||||||
|
import me.impy.aegis.crypto.KeyStoreHandle;
|
||||||
|
import me.impy.aegis.crypto.KeyStoreHandleException;
|
||||||
|
import me.impy.aegis.db.slots.FingerprintSlot;
|
||||||
|
import me.impy.aegis.db.slots.PasswordSlot;
|
||||||
|
import me.impy.aegis.db.slots.Slot;
|
||||||
|
import me.impy.aegis.db.slots.SlotException;
|
||||||
|
import me.impy.aegis.helpers.EditTextHelper;
|
||||||
|
import me.impy.aegis.helpers.FingerprintHelper;
|
||||||
|
import me.impy.aegis.helpers.FingerprintUiHelper;
|
||||||
|
import me.impy.aegis.ui.tasks.DerivationTask;
|
||||||
|
|
||||||
public class Dialogs {
|
public class Dialogs {
|
||||||
private Dialogs() {
|
private Dialogs() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AlertDialog showDeleteEntryDialog(Activity activity, DialogInterface.OnClickListener onDelete) {
|
public static void showDeleteEntryDialog(Activity activity, DialogInterface.OnClickListener onDelete) {
|
||||||
return new AlertDialog.Builder(activity)
|
new AlertDialog.Builder(activity)
|
||||||
.setTitle(activity.getString(R.string.delete_entry))
|
.setTitle(activity.getString(R.string.delete_entry))
|
||||||
.setMessage(activity.getString(R.string.delete_entry_description))
|
.setMessage(activity.getString(R.string.delete_entry_description))
|
||||||
.setPositiveButton(android.R.string.yes, onDelete)
|
.setPositiveButton(android.R.string.yes, onDelete)
|
||||||
|
@ -20,12 +43,115 @@ public class Dialogs {
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AlertDialog showDiscardDialog(Activity activity, DialogInterface.OnClickListener onSave, DialogInterface.OnClickListener onDiscard) {
|
public static void showDiscardDialog(Activity activity, DialogInterface.OnClickListener onSave, DialogInterface.OnClickListener onDiscard) {
|
||||||
return new AlertDialog.Builder(activity)
|
new AlertDialog.Builder(activity)
|
||||||
.setTitle(activity.getString(R.string.discard_changes))
|
.setTitle(activity.getString(R.string.discard_changes))
|
||||||
.setMessage(activity.getString(R.string.discard_changes_description))
|
.setMessage(activity.getString(R.string.discard_changes_description))
|
||||||
.setPositiveButton(R.string.save, onSave)
|
.setPositiveButton(R.string.save, onSave)
|
||||||
.setNegativeButton(R.string.discard, onDiscard)
|
.setNegativeButton(R.string.discard, onDiscard)
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void showSetPasswordDialog(Activity activity, Dialogs.SlotListener listener) {
|
||||||
|
View view = activity.getLayoutInflater().inflate(R.layout.dialog_password, null);
|
||||||
|
EditText textPassword = view.findViewById(R.id.text_password);
|
||||||
|
EditText textPasswordConfirm = view.findViewById(R.id.text_password_confirm);
|
||||||
|
|
||||||
|
AlertDialog alert = new AlertDialog.Builder(activity)
|
||||||
|
.setTitle(R.string.set_password)
|
||||||
|
.setView(view)
|
||||||
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.create();
|
||||||
|
|
||||||
|
final Button[] buttonOK = new Button[1];
|
||||||
|
alert.setOnShowListener(dialog -> {
|
||||||
|
buttonOK[0] = alert.getButton(AlertDialog.BUTTON_POSITIVE);
|
||||||
|
buttonOK[0].setEnabled(false);
|
||||||
|
|
||||||
|
// replace the default listener
|
||||||
|
buttonOK[0].setOnClickListener(v -> {
|
||||||
|
if (!EditTextHelper.areEditTextsEqual(textPassword, textPasswordConfirm)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char[] password = EditTextHelper.getEditTextChars(textPassword);
|
||||||
|
PasswordSlot slot = new PasswordSlot();
|
||||||
|
DerivationTask task = new DerivationTask(activity, key -> {
|
||||||
|
Cipher cipher;
|
||||||
|
try {
|
||||||
|
cipher = Slot.createEncryptCipher(key);
|
||||||
|
} catch (SlotException e) {
|
||||||
|
listener.onException(e);
|
||||||
|
dialog.cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
listener.onSlotResult(slot, cipher);
|
||||||
|
dialog.dismiss();
|
||||||
|
});
|
||||||
|
task.execute(new DerivationTask.Params(slot, password));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
TextWatcher watcher = new TextWatcher() {
|
||||||
|
public void onTextChanged(CharSequence c, int start, int before, int count) {
|
||||||
|
boolean equal = EditTextHelper.areEditTextsEqual(textPassword, textPasswordConfirm);
|
||||||
|
buttonOK[0].setEnabled(equal);
|
||||||
|
}
|
||||||
|
public void beforeTextChanged(CharSequence c, int start, int count, int after) { }
|
||||||
|
public void afterTextChanged(Editable c) { }
|
||||||
|
};
|
||||||
|
textPassword.addTextChangedListener(watcher);
|
||||||
|
textPasswordConfirm.addTextChangedListener(watcher);
|
||||||
|
|
||||||
|
alert.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void showFingerprintDialog(Activity activity, Dialogs.SlotListener listener) {
|
||||||
|
View view = activity.getLayoutInflater().inflate(R.layout.dialog_fingerprint, null);
|
||||||
|
TextView textFingerprint = view.findViewById(R.id.text_fingerprint);
|
||||||
|
SwirlView imgFingerprint = view.findViewById(R.id.img_fingerprint);
|
||||||
|
|
||||||
|
Cipher cipher;
|
||||||
|
FingerprintSlot slot;
|
||||||
|
final FingerprintUiHelper[] helper = new FingerprintUiHelper[1];
|
||||||
|
FingerprintManager manager = FingerprintHelper.getManager(activity);
|
||||||
|
|
||||||
|
try {
|
||||||
|
slot = new FingerprintSlot();
|
||||||
|
SecretKey key = new KeyStoreHandle().generateKey(slot.getUUID().toString());
|
||||||
|
cipher = Slot.createEncryptCipher(key);
|
||||||
|
} catch (KeyStoreHandleException | SlotException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
AlertDialog dialog = new AlertDialog.Builder(activity)
|
||||||
|
.setTitle(R.string.register_fingerprint)
|
||||||
|
.setView(view)
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.setOnDismissListener(d -> {
|
||||||
|
helper[0].stopListening();
|
||||||
|
})
|
||||||
|
.show();
|
||||||
|
|
||||||
|
helper[0] = new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, new FingerprintUiHelper.Callback() {
|
||||||
|
@Override
|
||||||
|
public void onAuthenticated() {
|
||||||
|
listener.onSlotResult(slot, cipher);
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError() {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
helper[0].startListening(new FingerprintManager.CryptoObject(cipher));
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface SlotListener {
|
||||||
|
void onSlotResult(Slot slot, Cipher cipher);
|
||||||
|
void onException(Exception e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,82 +0,0 @@
|
||||||
package me.impy.aegis.ui.dialogs;
|
|
||||||
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.hardware.fingerprint.FingerprintManager;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.mattprecious.swirl.SwirlView;
|
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.crypto.SecretKey;
|
|
||||||
|
|
||||||
import me.impy.aegis.R;
|
|
||||||
import me.impy.aegis.crypto.KeyStoreHandle;
|
|
||||||
import me.impy.aegis.crypto.KeyStoreHandleException;
|
|
||||||
import me.impy.aegis.db.slots.FingerprintSlot;
|
|
||||||
import me.impy.aegis.db.slots.Slot;
|
|
||||||
import me.impy.aegis.db.slots.SlotException;
|
|
||||||
import me.impy.aegis.helpers.FingerprintHelper;
|
|
||||||
import me.impy.aegis.helpers.FingerprintUiHelper;
|
|
||||||
|
|
||||||
public class FingerprintDialogFragment extends SlotDialogFragment implements FingerprintUiHelper.Callback {
|
|
||||||
private Cipher _cipher;
|
|
||||||
private FingerprintUiHelper _helper;
|
|
||||||
private FingerprintSlot _slot;
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
||||||
View view = getActivity().getLayoutInflater().inflate(R.layout.dialog_fingerprint, null);
|
|
||||||
TextView textFingerprint = view.findViewById(R.id.text_fingerprint);
|
|
||||||
SwirlView imgFingerprint = view.findViewById(R.id.img_fingerprint);
|
|
||||||
|
|
||||||
FingerprintManager manager = FingerprintHelper.getManager(getContext());
|
|
||||||
try {
|
|
||||||
_slot = new FingerprintSlot();
|
|
||||||
SecretKey key = new KeyStoreHandle().generateKey(_slot.getUUID().toString());
|
|
||||||
_cipher = Slot.createEncryptCipher(key);
|
|
||||||
_helper = new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, this);
|
|
||||||
} catch (KeyStoreHandleException | SlotException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new AlertDialog.Builder(getActivity())
|
|
||||||
.setTitle(R.string.register_fingerprint)
|
|
||||||
.setView(view)
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.create();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
|
|
||||||
if (_helper != null) {
|
|
||||||
_helper.startListening(new FingerprintManager.CryptoObject(_cipher));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPause() {
|
|
||||||
super.onPause();
|
|
||||||
|
|
||||||
if (_helper != null) {
|
|
||||||
_helper.stopListening();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAuthenticated() {
|
|
||||||
getListener().onSlotResult(_slot, _cipher);
|
|
||||||
dismiss();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
package me.impy.aegis.ui.dialogs;
|
|
||||||
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import android.text.Editable;
|
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.EditText;
|
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
|
|
||||||
import me.impy.aegis.R;
|
|
||||||
import me.impy.aegis.db.slots.PasswordSlot;
|
|
||||||
import me.impy.aegis.db.slots.Slot;
|
|
||||||
import me.impy.aegis.db.slots.SlotException;
|
|
||||||
import me.impy.aegis.helpers.EditTextHelper;
|
|
||||||
import me.impy.aegis.ui.tasks.DerivationTask;
|
|
||||||
|
|
||||||
public class PasswordDialogFragment extends SlotDialogFragment {
|
|
||||||
private Button _buttonOK;
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
||||||
View view = getActivity().getLayoutInflater().inflate(R.layout.dialog_password, null);
|
|
||||||
EditText textPassword = view.findViewById(R.id.text_password);
|
|
||||||
EditText textPasswordConfirm = view.findViewById(R.id.text_password_confirm);
|
|
||||||
|
|
||||||
AlertDialog alert = new AlertDialog.Builder(getActivity())
|
|
||||||
.setTitle(R.string.set_password)
|
|
||||||
.setView(view)
|
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.create();
|
|
||||||
|
|
||||||
alert.setOnShowListener(dialog -> {
|
|
||||||
_buttonOK = alert.getButton(AlertDialog.BUTTON_POSITIVE);
|
|
||||||
_buttonOK.setEnabled(false);
|
|
||||||
|
|
||||||
// replace the default listener
|
|
||||||
_buttonOK.setOnClickListener(v -> {
|
|
||||||
if (!EditTextHelper.areEditTextsEqual(textPassword, textPasswordConfirm)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
char[] password = EditTextHelper.getEditTextChars(textPassword);
|
|
||||||
PasswordSlot slot = new PasswordSlot();
|
|
||||||
DerivationTask task = new DerivationTask(getActivity(), key -> {
|
|
||||||
Cipher cipher;
|
|
||||||
try {
|
|
||||||
cipher = Slot.createEncryptCipher(key);
|
|
||||||
} catch (SlotException e) {
|
|
||||||
getListener().onException(e);
|
|
||||||
dialog.cancel();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
getListener().onSlotResult(slot, cipher);
|
|
||||||
dialog.dismiss();
|
|
||||||
});
|
|
||||||
task.execute(new DerivationTask.Params(slot, password));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
TextWatcher watcher = new TextWatcher() {
|
|
||||||
public void onTextChanged(CharSequence c, int start, int before, int count) {
|
|
||||||
boolean equal = EditTextHelper.areEditTextsEqual(textPassword, textPasswordConfirm);
|
|
||||||
_buttonOK.setEnabled(equal);
|
|
||||||
}
|
|
||||||
public void beforeTextChanged(CharSequence c, int start, int count, int after) { }
|
|
||||||
public void afterTextChanged(Editable c) { }
|
|
||||||
};
|
|
||||||
textPassword.addTextChangedListener(watcher);
|
|
||||||
textPasswordConfirm.addTextChangedListener(watcher);
|
|
||||||
|
|
||||||
return alert;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
package me.impy.aegis.ui.dialogs;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import androidx.fragment.app.DialogFragment;
|
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
|
|
||||||
import me.impy.aegis.db.slots.Slot;
|
|
||||||
|
|
||||||
public class SlotDialogFragment extends DialogFragment {
|
|
||||||
private Listener _listener;
|
|
||||||
|
|
||||||
protected Listener getListener() {
|
|
||||||
return _listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Context context) {
|
|
||||||
super.onAttach(context);
|
|
||||||
|
|
||||||
try {
|
|
||||||
_listener = (Listener) context;
|
|
||||||
} catch (ClassCastException e) {
|
|
||||||
throw new ClassCastException(context.toString() + " must implement SlotDialogFragment.Listener");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface Listener {
|
|
||||||
void onSlotResult(Slot slot, Cipher cipher);
|
|
||||||
void onException(Exception e);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -25,6 +25,34 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:scrollbars="vertical"/>
|
android:scrollbars="vertical"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/button_add_password"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingTop="12.5dp"
|
||||||
|
android:paddingBottom="12.5dp"
|
||||||
|
android:paddingStart="10dp"
|
||||||
|
android:paddingEnd="10dp"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:foreground="?android:attr/selectableItemBackground">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/ic_plus_black_24dp"
|
||||||
|
android:tint="@color/colorAccent"
|
||||||
|
android:layout_marginEnd="15dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/add_password"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/button_add_fingerprint"
|
android:id="@+id/button_add_fingerprint"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
@ -27,6 +27,8 @@
|
||||||
<string name="pref_secure_screen_summary">Block screenshots and other attempts to capture the screen within the app</string>
|
<string name="pref_secure_screen_summary">Block screenshots and other attempts to capture the screen within the app</string>
|
||||||
<string name="pref_encryption_title">Encryption</string>
|
<string name="pref_encryption_title">Encryption</string>
|
||||||
<string name="pref_encryption_summary">Encrypt the database and unlock it with a password or fingerprint</string>
|
<string name="pref_encryption_summary">Encrypt the database and unlock it with a password or fingerprint</string>
|
||||||
|
<string name="pref_fingerprint_title">Fingerprint</string>
|
||||||
|
<string name="pref_fingerprint_summary">Allow fingerprints registered on this device to unlock the vault</string>
|
||||||
<string name="pref_set_password_title">Change password</string>
|
<string name="pref_set_password_title">Change password</string>
|
||||||
<string name="pref_set_password_summary">Set a new password which you will need to unlock your vault</string>
|
<string name="pref_set_password_summary">Set a new password which you will need to unlock your vault</string>
|
||||||
|
|
||||||
|
@ -81,6 +83,7 @@
|
||||||
<string name="scan">Scan QR code</string>
|
<string name="scan">Scan QR code</string>
|
||||||
<string name="enter_manually">Enter manually</string>
|
<string name="enter_manually">Enter manually</string>
|
||||||
<string name="add_fingerprint">Add fingerprint</string>
|
<string name="add_fingerprint">Add fingerprint</string>
|
||||||
|
<string name="add_password">Add password</string>
|
||||||
<string name="slots_warning">The vault is only as secure as your weakest secret. When a new fingerprint is added to your device, you will to reactivate fingerprint authentication within Aegis.</string>
|
<string name="slots_warning">The vault is only as secure as your weakest secret. When a new fingerprint is added to your device, you will to reactivate fingerprint authentication within Aegis.</string>
|
||||||
<string name="copy">Copy</string>
|
<string name="copy">Copy</string>
|
||||||
<string name="edit">Edit</string>
|
<string name="edit">Edit</string>
|
||||||
|
@ -122,7 +125,8 @@
|
||||||
<string name="exporting_database_error">An error occurred while trying to export the database</string>
|
<string name="exporting_database_error">An error occurred while trying to export the database</string>
|
||||||
<string name="export_database_location">The database has been exported to:</string>
|
<string name="export_database_location">The database has been exported to:</string>
|
||||||
<string name="export_warning">This action will export the database out of Aegis\' private storage.</string>
|
<string name="export_warning">This action will export the database out of Aegis\' private storage.</string>
|
||||||
<string name="encryption_set_password_error">An error occurred while trying to set the password:</string>
|
<string name="encryption_set_password_error">An error occurred while trying to set the password: </string>
|
||||||
|
<string name="encryption_enable_fingerprint_error">An error occurred while trying to enable fingerprint unlock: </string>
|
||||||
<string name="no_cameras_available">No cameras available</string>
|
<string name="no_cameras_available">No cameras available</string>
|
||||||
<string name="read_qr_error">An error occurred while trying to read the QR code</string>
|
<string name="read_qr_error">An error occurred while trying to read the QR code</string>
|
||||||
<string name="authentication_method_raw">Raw</string>
|
<string name="authentication_method_raw">Raw</string>
|
||||||
|
|
|
@ -53,6 +53,13 @@
|
||||||
android:dependency="pref_encryption"
|
android:dependency="pref_encryption"
|
||||||
app:iconSpaceReserved="false"/>
|
app:iconSpaceReserved="false"/>
|
||||||
|
|
||||||
|
<me.impy.aegis.ui.preferences.SwitchPreference
|
||||||
|
android:key="pref_fingerprint"
|
||||||
|
android:title="@string/pref_fingerprint_title"
|
||||||
|
android:summary="@string/pref_fingerprint_summary"
|
||||||
|
android:persistent="false"
|
||||||
|
app:iconSpaceReserved="false"/>
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:key="pref_slots"
|
android:key="pref_slots"
|
||||||
android:title="@string/pref_slots_title"
|
android:title="@string/pref_slots_title"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue