mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-05-14 22:12:55 +00:00
Add an option to set a separate password for backups and exports
This commit is contained in:
parent
d40ebf0f67
commit
a492bcbde3
14 changed files with 243 additions and 33 deletions
|
@ -9,12 +9,16 @@ import android.os.ParcelFileDescriptor;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.beemdevelopment.aegis.util.IOUtils;
|
import com.beemdevelopment.aegis.util.IOUtils;
|
||||||
|
import com.beemdevelopment.aegis.vault.VaultFile;
|
||||||
import com.beemdevelopment.aegis.vault.VaultRepository;
|
import com.beemdevelopment.aegis.vault.VaultRepository;
|
||||||
|
import com.beemdevelopment.aegis.vault.VaultRepositoryException;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
public class AegisBackupAgent extends BackupAgent {
|
public class AegisBackupAgent extends BackupAgent {
|
||||||
private static final String TAG = AegisBackupAgent.class.getSimpleName();
|
private static final String TAG = AegisBackupAgent.class.getSimpleName();
|
||||||
|
@ -46,14 +50,17 @@ public class AegisBackupAgent extends BackupAgent {
|
||||||
}
|
}
|
||||||
|
|
||||||
// first copy the vault to the files/backup directory
|
// first copy the vault to the files/backup directory
|
||||||
createBackupDir();
|
|
||||||
File vaultBackupFile = getVaultBackupFile();
|
File vaultBackupFile = getVaultBackupFile();
|
||||||
try {
|
try (OutputStream outputStream = new FileOutputStream(vaultBackupFile)) {
|
||||||
VaultRepository.copyFileTo(this, vaultBackupFile);
|
createBackupDir();
|
||||||
} catch (IOException e) {
|
|
||||||
|
VaultFile vaultFile = VaultRepository.readVaultFile(this);
|
||||||
|
byte[] bytes = vaultFile.exportable().toBytes();
|
||||||
|
outputStream.write(bytes);
|
||||||
|
} catch (VaultRepositoryException | IOException e) {
|
||||||
Log.e(TAG, String.format("onFullBackup() failed: %s", e));
|
Log.e(TAG, String.format("onFullBackup() failed: %s", e));
|
||||||
deleteBackupDir();
|
deleteBackupDir();
|
||||||
throw e;
|
throw new IOException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// then call the original implementation so that fullBackupContent specified in AndroidManifest is read
|
// then call the original implementation so that fullBackupContent specified in AndroidManifest is read
|
||||||
|
|
|
@ -114,7 +114,7 @@ public class Dialogs {
|
||||||
.create());
|
.create());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void showSetPasswordDialog(ComponentActivity activity, Dialogs.SlotListener listener) {
|
public static void showSetPasswordDialog(ComponentActivity activity, PasswordSlotListener listener) {
|
||||||
Zxcvbn zxcvbn = new Zxcvbn();
|
Zxcvbn zxcvbn = new Zxcvbn();
|
||||||
View view = activity.getLayoutInflater().inflate(R.layout.dialog_password, null);
|
View view = activity.getLayoutInflater().inflate(R.layout.dialog_password, null);
|
||||||
EditText textPassword = view.findViewById(R.id.text_password);
|
EditText textPassword = view.findViewById(R.id.text_password);
|
||||||
|
@ -460,8 +460,8 @@ public class Dialogs {
|
||||||
void onTextInputResult(char[] text);
|
void onTextInputResult(char[] text);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface SlotListener {
|
public interface PasswordSlotListener {
|
||||||
void onSlotResult(Slot slot, Cipher cipher);
|
void onSlotResult(PasswordSlot slot, Cipher cipher);
|
||||||
void onException(Exception e);
|
void onException(Exception e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ import com.beemdevelopment.aegis.vault.VaultBackupManager;
|
||||||
import com.beemdevelopment.aegis.vault.VaultFileCredentials;
|
import com.beemdevelopment.aegis.vault.VaultFileCredentials;
|
||||||
import com.beemdevelopment.aegis.vault.VaultRepository;
|
import com.beemdevelopment.aegis.vault.VaultRepository;
|
||||||
import com.beemdevelopment.aegis.vault.VaultRepositoryException;
|
import com.beemdevelopment.aegis.vault.VaultRepositoryException;
|
||||||
import com.beemdevelopment.aegis.vault.slots.Slot;
|
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
|
||||||
import com.beemdevelopment.aegis.vault.slots.SlotException;
|
import com.beemdevelopment.aegis.vault.slots.SlotException;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -267,9 +267,9 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
|
||||||
if (_vaultManager.getVault().isEncryptionEnabled()) {
|
if (_vaultManager.getVault().isEncryptionEnabled()) {
|
||||||
cb.exportVault(stream -> _vaultManager.getVault().export(stream));
|
cb.exportVault(stream -> _vaultManager.getVault().export(stream));
|
||||||
} else {
|
} else {
|
||||||
Dialogs.showSetPasswordDialog(requireActivity(), new Dialogs.SlotListener() {
|
Dialogs.showSetPasswordDialog(requireActivity(), new Dialogs.PasswordSlotListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onSlotResult(Slot slot, Cipher cipher) {
|
public void onSlotResult(PasswordSlot slot, Cipher cipher) {
|
||||||
VaultFileCredentials creds = new VaultFileCredentials();
|
VaultFileCredentials creds = new VaultFileCredentials();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -24,6 +24,7 @@ import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
||||||
import com.beemdevelopment.aegis.ui.preferences.SwitchPreference;
|
import com.beemdevelopment.aegis.ui.preferences.SwitchPreference;
|
||||||
import com.beemdevelopment.aegis.ui.tasks.PasswordSlotDecryptTask;
|
import com.beemdevelopment.aegis.ui.tasks.PasswordSlotDecryptTask;
|
||||||
import com.beemdevelopment.aegis.vault.VaultFileCredentials;
|
import com.beemdevelopment.aegis.vault.VaultFileCredentials;
|
||||||
|
import com.beemdevelopment.aegis.vault.VaultRepository;
|
||||||
import com.beemdevelopment.aegis.vault.VaultRepositoryException;
|
import com.beemdevelopment.aegis.vault.VaultRepositoryException;
|
||||||
import com.beemdevelopment.aegis.vault.slots.BiometricSlot;
|
import com.beemdevelopment.aegis.vault.slots.BiometricSlot;
|
||||||
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
|
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
|
||||||
|
@ -33,6 +34,7 @@ import com.beemdevelopment.aegis.vault.slots.SlotList;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
|
|
||||||
|
@ -43,6 +45,8 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
||||||
private Preference _setPasswordPreference;
|
private Preference _setPasswordPreference;
|
||||||
private Preference _passwordReminderPreference;
|
private Preference _passwordReminderPreference;
|
||||||
private SwitchPreferenceCompat _pinKeyboardPreference;
|
private SwitchPreferenceCompat _pinKeyboardPreference;
|
||||||
|
private SwitchPreference _backupPasswordPreference;
|
||||||
|
private Preference _backupPasswordChangePreference;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
|
@ -161,7 +165,7 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
||||||
|
|
||||||
Dialogs.showPasswordInputDialog(requireContext(), R.string.set_password_confirm, R.string.pin_keyboard_description, password -> {
|
Dialogs.showPasswordInputDialog(requireContext(), R.string.set_password_confirm, R.string.pin_keyboard_description, password -> {
|
||||||
if (isDigitsOnly(new String(password))) {
|
if (isDigitsOnly(new String(password))) {
|
||||||
List<PasswordSlot> slots = _vaultManager.getVault().getCredentials().getSlots().findAll(PasswordSlot.class);
|
List<PasswordSlot> slots = getRegularPasswordSlots(_vaultManager.getVault().getCredentials().getSlots());
|
||||||
PasswordSlotDecryptTask.Params params = new PasswordSlotDecryptTask.Params(slots, password);
|
PasswordSlotDecryptTask.Params params = new PasswordSlotDecryptTask.Params(slots, password);
|
||||||
PasswordSlotDecryptTask task = new PasswordSlotDecryptTask(requireContext(), new PasswordConfirmationListener());
|
PasswordSlotDecryptTask task = new PasswordSlotDecryptTask(requireContext(), new PasswordConfirmationListener());
|
||||||
task.execute(getLifecycle(), params);
|
task.execute(getLifecycle(), params);
|
||||||
|
@ -232,33 +236,88 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
||||||
Dialogs.showSecureDialog(builder.create());
|
Dialogs.showSecureDialog(builder.create());
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_backupPasswordPreference = requirePreference("pref_backup_password");
|
||||||
|
_backupPasswordPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
if (!isBackupPasswordSet()) {
|
||||||
|
Dialogs.showSetPasswordDialog(requireActivity(), new SetBackupPasswordListener());
|
||||||
|
} else {
|
||||||
|
SlotList slots = _vaultManager.getVault().getCredentials().getSlots();
|
||||||
|
for (Slot slot : getBackupPasswordSlots(slots)) {
|
||||||
|
slots.remove(slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
saveAndBackupVault();
|
||||||
|
updateEncryptionPreferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
_backupPasswordChangePreference = requirePreference("pref_backup_password_change");
|
||||||
|
_backupPasswordChangePreference.setOnPreferenceClickListener(preference -> {
|
||||||
|
Dialogs.showSetPasswordDialog(requireActivity(), new SetBackupPasswordListener());
|
||||||
|
return false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateEncryptionPreferences() {
|
private void updateEncryptionPreferences() {
|
||||||
boolean encrypted = _vaultManager.getVault().isEncryptionEnabled();
|
boolean encrypted = _vaultManager.getVault().isEncryptionEnabled();
|
||||||
|
boolean backupPasswordSet = isBackupPasswordSet();
|
||||||
_encryptionPreference.setChecked(encrypted, true);
|
_encryptionPreference.setChecked(encrypted, true);
|
||||||
_setPasswordPreference.setVisible(encrypted);
|
_setPasswordPreference.setVisible(encrypted);
|
||||||
_biometricsPreference.setVisible(encrypted);
|
_biometricsPreference.setVisible(encrypted);
|
||||||
_autoLockPreference.setVisible(encrypted);
|
_autoLockPreference.setVisible(encrypted);
|
||||||
_pinKeyboardPreference.setVisible(encrypted);
|
_pinKeyboardPreference.setVisible(encrypted);
|
||||||
|
_backupPasswordPreference.getParent().setVisible(encrypted);
|
||||||
|
_backupPasswordPreference.setChecked(backupPasswordSet, true);
|
||||||
|
_backupPasswordChangePreference.setVisible(backupPasswordSet);
|
||||||
|
|
||||||
if (encrypted) {
|
if (encrypted) {
|
||||||
SlotList slots = _vaultManager.getVault().getCredentials().getSlots();
|
SlotList slots = _vaultManager.getVault().getCredentials().getSlots();
|
||||||
boolean multiPassword = slots.findAll(PasswordSlot.class).size() > 1;
|
boolean multiBackupPassword = getBackupPasswordSlots(slots).size() > 1;
|
||||||
|
boolean multiPassword = getRegularPasswordSlots(slots).size() > 1;
|
||||||
boolean multiBio = slots.findAll(BiometricSlot.class).size() > 1;
|
boolean multiBio = slots.findAll(BiometricSlot.class).size() > 1;
|
||||||
boolean canUseBio = BiometricsHelper.isAvailable(requireContext());
|
boolean canUseBio = BiometricsHelper.isAvailable(requireContext());
|
||||||
_setPasswordPreference.setEnabled(!multiPassword);
|
_setPasswordPreference.setEnabled(!multiPassword);
|
||||||
_biometricsPreference.setEnabled(canUseBio && !multiBio);
|
_biometricsPreference.setEnabled(canUseBio && !multiBio);
|
||||||
_biometricsPreference.setChecked(slots.has(BiometricSlot.class), true);
|
_biometricsPreference.setChecked(slots.has(BiometricSlot.class), true);
|
||||||
_passwordReminderPreference.setVisible(slots.has(BiometricSlot.class));
|
_passwordReminderPreference.setVisible(slots.has(BiometricSlot.class));
|
||||||
|
_backupPasswordChangePreference.setEnabled(!multiBackupPassword);
|
||||||
} else {
|
} else {
|
||||||
_setPasswordPreference.setEnabled(false);
|
_setPasswordPreference.setEnabled(false);
|
||||||
_biometricsPreference.setEnabled(false);
|
_biometricsPreference.setEnabled(false);
|
||||||
_biometricsPreference.setChecked(false, true);
|
_biometricsPreference.setChecked(false, true);
|
||||||
_passwordReminderPreference.setVisible(false);
|
_passwordReminderPreference.setVisible(false);
|
||||||
|
_backupPasswordChangePreference.setEnabled(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isBackupPasswordSet() {
|
||||||
|
VaultRepository vault = _vaultManager.getVault();
|
||||||
|
if (!vault.isEncryptionEnabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getBackupPasswordSlots(vault.getCredentials().getSlots()).size() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<PasswordSlot> getBackupPasswordSlots(SlotList slots) {
|
||||||
|
return getPasswordSlots(slots, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<PasswordSlot> getRegularPasswordSlots(SlotList slots) {
|
||||||
|
return getPasswordSlots(slots, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<PasswordSlot> getPasswordSlots(SlotList slots, boolean isBackup) {
|
||||||
|
return slots
|
||||||
|
.findAll(PasswordSlot.class)
|
||||||
|
.stream()
|
||||||
|
.filter(s -> s.isBackup() == isBackup)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
private String getPasswordReminderSummary() {
|
private String getPasswordReminderSummary() {
|
||||||
PassReminderFreq freq = _prefs.getPasswordReminderFrequency();
|
PassReminderFreq freq = _prefs.getPasswordReminderFrequency();
|
||||||
if (freq == PassReminderFreq.NEVER) {
|
if (freq == PassReminderFreq.NEVER) {
|
||||||
|
@ -291,9 +350,9 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
||||||
return getString(R.string.pref_auto_lock_summary, builder.toString());
|
return getString(R.string.pref_auto_lock_summary, builder.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SetPasswordListener implements Dialogs.SlotListener {
|
private class SetPasswordListener implements Dialogs.PasswordSlotListener {
|
||||||
@Override
|
@Override
|
||||||
public void onSlotResult(Slot slot, Cipher cipher) {
|
public void onSlotResult(PasswordSlot slot, Cipher cipher) {
|
||||||
VaultFileCredentials creds = _vaultManager.getVault().getCredentials();
|
VaultFileCredentials creds = _vaultManager.getVault().getCredentials();
|
||||||
SlotList slots = creds.getSlots();
|
SlotList slots = creds.getSlots();
|
||||||
|
|
||||||
|
@ -331,6 +390,43 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class SetBackupPasswordListener implements Dialogs.PasswordSlotListener {
|
||||||
|
@Override
|
||||||
|
public void onSlotResult(PasswordSlot slot, Cipher cipher) {
|
||||||
|
slot.setIsBackup(true);
|
||||||
|
|
||||||
|
VaultFileCredentials creds = _vaultManager.getVault().getCredentials();
|
||||||
|
SlotList slots = creds.getSlots();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// encrypt the master key for this slot
|
||||||
|
slot.setKey(creds.getKey(), cipher);
|
||||||
|
|
||||||
|
// remove the old backup password slot
|
||||||
|
for (Slot oldSlot : getBackupPasswordSlots(slots)) {
|
||||||
|
slots.remove(oldSlot);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the new backup password slot
|
||||||
|
slots.add(slot);
|
||||||
|
} catch (SlotException e) {
|
||||||
|
onException(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_vaultManager.getVault().setCredentials(creds);
|
||||||
|
saveAndBackupVault();
|
||||||
|
updateEncryptionPreferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException(Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
updateEncryptionPreferences();
|
||||||
|
Dialogs.showErrorDialog(requireContext(), R.string.encryption_set_password_error, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class RegisterBiometricsListener implements BiometricSlotInitializer.Listener {
|
private class RegisterBiometricsListener implements BiometricSlotInitializer.Listener {
|
||||||
@Override
|
@Override
|
||||||
public void onInitializeSlot(BiometricSlot slot, Cipher cipher) {
|
public void onInitializeSlot(BiometricSlot slot, Cipher cipher) {
|
||||||
|
@ -357,9 +453,9 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class EnableEncryptionListener implements Dialogs.SlotListener {
|
private class EnableEncryptionListener implements Dialogs.PasswordSlotListener {
|
||||||
@Override
|
@Override
|
||||||
public void onSlotResult(Slot slot, Cipher cipher) {
|
public void onSlotResult(PasswordSlot slot, Cipher cipher) {
|
||||||
VaultFileCredentials creds = new VaultFileCredentials();
|
VaultFileCredentials creds = new VaultFileCredentials();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -117,6 +117,21 @@ public class VaultFile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a copy of this VaultFile that's suitable for exporting.
|
||||||
|
* In case there's a backup password slot, any regular password slots are stripped.
|
||||||
|
*/
|
||||||
|
public VaultFile exportable() {
|
||||||
|
if (!isEncrypted()) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new VaultFile(getContent(), new VaultFile.Header(
|
||||||
|
getHeader().getSlots().exportable(),
|
||||||
|
getHeader().getParams()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
public static class Header {
|
public static class Header {
|
||||||
private SlotList _slots;
|
private SlotList _slots;
|
||||||
private CryptParameters _params;
|
private CryptParameters _params;
|
||||||
|
|
|
@ -37,4 +37,12 @@ public class VaultFileCredentials implements Serializable {
|
||||||
public SlotList getSlots() {
|
public SlotList getSlots() {
|
||||||
return _slots;
|
return _slots;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a copy of these VaultFileCredentials that is suitable for exporting.
|
||||||
|
* In case there's a backup password slot, any regular password slots are stripped.
|
||||||
|
*/
|
||||||
|
public VaultFileCredentials exportable() {
|
||||||
|
return new VaultFileCredentials(_key, _slots.exportable());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,9 @@ import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -198,7 +200,10 @@ public class VaultManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
File tempFile = File.createTempFile(VaultBackupManager.FILENAME_PREFIX, ".json", dir);
|
File tempFile = File.createTempFile(VaultBackupManager.FILENAME_PREFIX, ".json", dir);
|
||||||
VaultRepository.copyFileTo(_context, tempFile);
|
try (OutputStream outStream = new FileOutputStream(tempFile)) {
|
||||||
|
_repo.export(outStream);
|
||||||
|
}
|
||||||
|
|
||||||
_backups.scheduleBackup(tempFile, _prefs.getBackupsLocation(), _prefs.getBackupsVersionCount());
|
_backups.scheduleBackup(tempFile, _prefs.getBackupsLocation(), _prefs.getBackupsVersionCount());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new VaultRepositoryException(e);
|
throw new VaultRepositoryException(e);
|
||||||
|
|
|
@ -107,13 +107,6 @@ public class VaultRepository {
|
||||||
return new VaultRepository(context, vault, creds);
|
return new VaultRepository(context, vault, creds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void copyFileTo(Context context, File destFile) throws IOException {
|
|
||||||
try (InputStream inStream = VaultRepository.getAtomicFile(context).openRead();
|
|
||||||
OutputStream outStream = new FileOutputStream(destFile)) {
|
|
||||||
IOUtils.copy(inStream, outStream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void save() throws VaultRepositoryException {
|
void save() throws VaultRepositoryException {
|
||||||
try {
|
try {
|
||||||
JSONObject obj = _vault.toJson();
|
JSONObject obj = _vault.toJson();
|
||||||
|
@ -137,7 +130,7 @@ public class VaultRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exports the vault bt serializing it and writing it to the given OutputStream. If encryption
|
* Exports the vault by serializing it and writing it to the given OutputStream. If encryption
|
||||||
* is enabled, the vault will be encrypted automatically.
|
* is enabled, the vault will be encrypted automatically.
|
||||||
*/
|
*/
|
||||||
public void export(OutputStream stream) throws VaultRepositoryException {
|
public void export(OutputStream stream) throws VaultRepositoryException {
|
||||||
|
@ -149,6 +142,10 @@ public class VaultRepository {
|
||||||
* not null, it will be used to encrypt the vault first.
|
* not null, it will be used to encrypt the vault first.
|
||||||
*/
|
*/
|
||||||
public void export(OutputStream stream, VaultFileCredentials creds) throws VaultRepositoryException {
|
public void export(OutputStream stream, VaultFileCredentials creds) throws VaultRepositoryException {
|
||||||
|
if (creds != null) {
|
||||||
|
creds = creds.exportable();
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
VaultFile vaultFile = new VaultFile();
|
VaultFile vaultFile = new VaultFile();
|
||||||
if (creds != null) {
|
if (creds != null) {
|
||||||
|
|
|
@ -16,16 +16,19 @@ import javax.crypto.SecretKey;
|
||||||
|
|
||||||
public class PasswordSlot extends RawSlot {
|
public class PasswordSlot extends RawSlot {
|
||||||
private boolean _repaired;
|
private boolean _repaired;
|
||||||
|
private boolean _isBackup;
|
||||||
|
|
||||||
private SCryptParameters _params;
|
private SCryptParameters _params;
|
||||||
|
|
||||||
public PasswordSlot() {
|
public PasswordSlot() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected PasswordSlot(UUID uuid, byte[] key, CryptParameters keyParams, SCryptParameters scryptParams, boolean repaired) {
|
protected PasswordSlot(UUID uuid, byte[] key, CryptParameters keyParams, SCryptParameters scryptParams, boolean repaired, boolean isBackup) {
|
||||||
super(uuid, key, keyParams);
|
super(uuid, key, keyParams);
|
||||||
_params = scryptParams;
|
_params = scryptParams;
|
||||||
_repaired = repaired;
|
_repaired = repaired;
|
||||||
|
_isBackup = isBackup;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -37,6 +40,7 @@ public class PasswordSlot extends RawSlot {
|
||||||
obj.put("p", _params.getP());
|
obj.put("p", _params.getP());
|
||||||
obj.put("salt", Hex.encode(_params.getSalt()));
|
obj.put("salt", Hex.encode(_params.getSalt()));
|
||||||
obj.put("repaired", _repaired);
|
obj.put("repaired", _repaired);
|
||||||
|
obj.put("is_backup", _isBackup);
|
||||||
return obj;
|
return obj;
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
@ -70,8 +74,15 @@ public class PasswordSlot extends RawSlot {
|
||||||
return _repaired;
|
return _repaired;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SCryptParameters getSCryptParameters() {
|
/**
|
||||||
return _params;
|
* Reports whether this slot is a backup password slot.
|
||||||
|
*/
|
||||||
|
public boolean isBackup() {
|
||||||
|
return _isBackup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsBackup(boolean isBackup) {
|
||||||
|
_isBackup = isBackup;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -140,7 +140,8 @@ public abstract class Slot extends UUIDMap.Value {
|
||||||
Hex.decode(obj.getString("salt"))
|
Hex.decode(obj.getString("salt"))
|
||||||
);
|
);
|
||||||
boolean repaired = obj.optBoolean("repaired", false);
|
boolean repaired = obj.optBoolean("repaired", false);
|
||||||
slot = new PasswordSlot(uuid, key, keyParams, scryptParams, repaired);
|
boolean isBackup = obj.optBoolean("is_backup", false);
|
||||||
|
slot = new PasswordSlot(uuid, key, keyParams, scryptParams, repaired, isBackup);
|
||||||
break;
|
break;
|
||||||
case Slot.TYPE_BIOMETRIC:
|
case Slot.TYPE_BIOMETRIC:
|
||||||
slot = new BiometricSlot(uuid, key, keyParams);
|
slot = new BiometricSlot(uuid, key, keyParams);
|
||||||
|
|
|
@ -57,4 +57,31 @@ public class SlotList extends UUIDMap<Slot> {
|
||||||
public <T extends Slot> boolean has(Class<T> type) {
|
public <T extends Slot> boolean has(Class<T> type) {
|
||||||
return find(type) != null;
|
return find(type) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a copy of this SlotList that is suitable for exporting.
|
||||||
|
* In case there's a backup password slot, any regular password slots are stripped.
|
||||||
|
*/
|
||||||
|
public SlotList exportable() {
|
||||||
|
boolean hasBackupSlots = false;
|
||||||
|
for (Slot slot : this) {
|
||||||
|
if (slot instanceof PasswordSlot && ((PasswordSlot) slot).isBackup()) {
|
||||||
|
hasBackupSlots = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasBackupSlots) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
SlotList slots = new SlotList();
|
||||||
|
for (Slot slot : this) {
|
||||||
|
if (!(slot instanceof PasswordSlot) || ((PasswordSlot) slot).isBackup()) {
|
||||||
|
slots.add(slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return slots;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,11 @@
|
||||||
<string name="pref_auto_lock_type_back_button">The back button is pressed</string>
|
<string name="pref_auto_lock_type_back_button">The back button is pressed</string>
|
||||||
<string name="pref_auto_lock_type_minimize">The app is minimized</string>
|
<string name="pref_auto_lock_type_minimize">The app is minimized</string>
|
||||||
<string name="pref_auto_lock_type_device_lock">The device is locked</string>
|
<string name="pref_auto_lock_type_device_lock">The device is locked</string>
|
||||||
|
<string name="pref_backup_password_category">Backup & Export</string>
|
||||||
|
<string name="pref_backup_password_title">Separate password for backup & export</string>
|
||||||
|
<string name="pref_backup_password_summary">If enabled, the password that is used to unlock the app can\'t be used to decrypt backups and exports anymore.</string>
|
||||||
|
<string name="pref_backup_password_change_title">Change password for backup & export</string>
|
||||||
|
<string name="pref_backup_password_change_summary">Set a new password that\'ll be used to encrypt the vault during backup and export.</string>
|
||||||
<string name="pref_encryption_title">Encryption</string>
|
<string name="pref_encryption_title">Encryption</string>
|
||||||
<string name="pref_encryption_summary">Encrypt the vault and unlock it with a password or biometrics</string>
|
<string name="pref_encryption_summary">Encrypt the vault and unlock it with a password or biometrics</string>
|
||||||
<string name="pref_biometrics_title">Biometric unlock</string>
|
<string name="pref_biometrics_title">Biometric unlock</string>
|
||||||
|
|
|
@ -33,6 +33,23 @@
|
||||||
android:dependency="pref_biometrics"
|
android:dependency="pref_biometrics"
|
||||||
app:iconSpaceReserved="false"/>
|
app:iconSpaceReserved="false"/>
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
<PreferenceCategory
|
||||||
|
android:title="@string/pref_backup_password_category"
|
||||||
|
app:iconSpaceReserved="false">
|
||||||
|
<com.beemdevelopment.aegis.ui.preferences.SwitchPreference
|
||||||
|
android:key="pref_backup_password"
|
||||||
|
android:title="@string/pref_backup_password_title"
|
||||||
|
android:summary="@string/pref_backup_password_summary"
|
||||||
|
android:persistent="false"
|
||||||
|
app:iconSpaceReserved="false"/>
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:key="pref_backup_password_change"
|
||||||
|
android:title="@string/pref_backup_password_change_title"
|
||||||
|
android:summary="@string/pref_backup_password_change_summary"
|
||||||
|
android:dependency="pref_encryption"
|
||||||
|
app:iconSpaceReserved="false"/>
|
||||||
|
</PreferenceCategory>
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
android:title="@string/pref_section_behavior_title"
|
android:title="@string/pref_section_behavior_title"
|
||||||
app:iconSpaceReserved="false">
|
app:iconSpaceReserved="false">
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
package com.beemdevelopment.aegis.vault.slots;
|
package com.beemdevelopment.aegis.vault.slots;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
import static org.junit.Assert.assertNotEquals;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
|
|
||||||
import com.beemdevelopment.aegis.crypto.CryptoUtils;
|
import com.beemdevelopment.aegis.crypto.CryptoUtils;
|
||||||
import com.beemdevelopment.aegis.crypto.MasterKey;
|
import com.beemdevelopment.aegis.crypto.MasterKey;
|
||||||
import com.beemdevelopment.aegis.crypto.SCryptParameters;
|
import com.beemdevelopment.aegis.crypto.SCryptParameters;
|
||||||
|
@ -16,9 +20,6 @@ import javax.crypto.NoSuchPaddingException;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
|
||||||
import static org.junit.Assert.assertThrows;
|
|
||||||
|
|
||||||
public class SlotTest {
|
public class SlotTest {
|
||||||
private MasterKey _masterKey;
|
private MasterKey _masterKey;
|
||||||
|
|
||||||
|
@ -93,4 +94,24 @@ public class SlotTest {
|
||||||
garbledCiphertext[0] = (byte) ~garbledCiphertext[0];
|
garbledCiphertext[0] = (byte) ~garbledCiphertext[0];
|
||||||
assertThrows(SlotIntegrityException.class, () -> slot.getKey(decryptCipher));
|
assertThrows(SlotIntegrityException.class, () -> slot.getKey(decryptCipher));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPasswordSlotExclusion() {
|
||||||
|
SlotList slots = new SlotList();
|
||||||
|
PasswordSlot passSlot = new PasswordSlot();
|
||||||
|
PasswordSlot passSlot2 = new PasswordSlot();
|
||||||
|
slots.add(passSlot);
|
||||||
|
slots.add(passSlot2);
|
||||||
|
|
||||||
|
assertArrayEquals(slots.getValues().toArray(), slots.exportable().getValues().toArray());
|
||||||
|
|
||||||
|
SlotList backupSlots = new SlotList();
|
||||||
|
PasswordSlot backupSlot = new PasswordSlot();
|
||||||
|
backupSlot.setIsBackup(true);
|
||||||
|
slots.add(backupSlot);
|
||||||
|
backupSlots.add(backupSlot);
|
||||||
|
|
||||||
|
assertArrayEquals(backupSlots.getValues().toArray(), slots.exportable().getValues().toArray());
|
||||||
|
assertNotEquals(slots.getValues().toArray(), slots.exportable().getValues().toArray());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue