2019-12-25 19:21:34 +01:00
|
|
|
package com.beemdevelopment.aegis.vault;
|
2017-08-06 16:03:36 +02:00
|
|
|
|
|
|
|
import android.content.Context;
|
2019-06-12 01:49:26 +02:00
|
|
|
import android.content.Intent;
|
2017-08-06 16:03:36 +02:00
|
|
|
|
2020-02-02 13:23:45 +01:00
|
|
|
import androidx.core.util.AtomicFile;
|
|
|
|
|
2020-01-04 22:06:59 +01:00
|
|
|
import com.beemdevelopment.aegis.Preferences;
|
2019-06-12 01:49:26 +02:00
|
|
|
import com.beemdevelopment.aegis.services.NotificationService;
|
2019-04-04 14:07:36 +02:00
|
|
|
|
2018-02-13 22:06:24 +01:00
|
|
|
import org.json.JSONObject;
|
|
|
|
|
2017-12-10 19:19:48 +01:00
|
|
|
import java.io.File;
|
|
|
|
import java.io.FileOutputStream;
|
|
|
|
import java.io.IOException;
|
2020-01-04 18:40:25 +01:00
|
|
|
import java.io.OutputStream;
|
2018-12-11 11:44:36 +01:00
|
|
|
import java.text.Collator;
|
Introduce UUIDMap for storing objects that are keyed by a UUID
This patch introduces the new ``UUIDMap`` type, reducing code duplication and
making UUID lookups faster. We currently already use UUIDs as the identifier for
the ``DatabaseEntry`` and ``Slot`` types, but the way lookups by UUID work are
kind of ugly, as we simply iterate over the list until we find a match. As we're
probably going to have more types like this soon (groups and icons, for
example), I figured it'd be good to abstract this away into a separate type and
make it a map instead of a list.
The only thing that has gotten slower is the ``swap`` method. The internal
``LinkedHashMap`` retains insertion order with a linked list, but does not know
about the position of the values, so we basically have to copy the entire map to
simply swap two values. I don't think it's too big of a deal, because swap
operations still take less than a millisecond even with large vaults, but
suggestions for improving this are welcome.
I had to update gradle and JUnit to be able to use the new ``assertThrows``
assertion method, so this patch includes that as well.
2019-06-10 18:25:44 +02:00
|
|
|
import java.util.Collection;
|
2018-12-11 11:44:36 +01:00
|
|
|
import java.util.TreeSet;
|
2020-05-02 18:51:33 +02:00
|
|
|
import java.util.UUID;
|
2017-08-06 16:03:36 +02:00
|
|
|
|
2019-12-25 19:21:34 +01:00
|
|
|
public class VaultManager {
|
2020-01-04 22:06:59 +01:00
|
|
|
public static final String FILENAME = "aegis.json";
|
|
|
|
public static final String FILENAME_EXPORT = "aegis-export.json";
|
|
|
|
public static final String FILENAME_EXPORT_PLAIN = "aegis-export-plain.json";
|
2017-12-04 22:08:50 +01:00
|
|
|
|
2019-12-25 19:21:34 +01:00
|
|
|
private Vault _vault;
|
|
|
|
private VaultFile _file;
|
|
|
|
private VaultFileCredentials _creds;
|
2018-10-06 22:23:38 +02:00
|
|
|
private boolean _encrypt;
|
|
|
|
|
2017-08-06 16:03:36 +02:00
|
|
|
private Context _context;
|
2020-01-04 22:06:59 +01:00
|
|
|
private Preferences _prefs;
|
|
|
|
private VaultBackupManager _backups;
|
2017-08-06 16:03:36 +02:00
|
|
|
|
2019-12-25 19:21:34 +01:00
|
|
|
public VaultManager(Context context) {
|
2017-08-06 16:03:36 +02:00
|
|
|
_context = context;
|
2020-01-04 22:06:59 +01:00
|
|
|
_prefs = new Preferences(context);
|
|
|
|
_backups = new VaultBackupManager(context);
|
2017-08-06 16:03:36 +02:00
|
|
|
}
|
|
|
|
|
2017-12-25 00:16:24 +01:00
|
|
|
public boolean fileExists() {
|
|
|
|
File file = new File(_context.getFilesDir(), FILENAME);
|
|
|
|
return file.exists() && file.isFile();
|
|
|
|
}
|
|
|
|
|
2019-12-25 19:21:34 +01:00
|
|
|
public void load() throws VaultManagerException {
|
2017-12-24 22:29:32 +01:00
|
|
|
assertState(true, false);
|
|
|
|
|
2020-02-02 13:23:45 +01:00
|
|
|
AtomicFile file = new AtomicFile(new File(_context.getFilesDir(), FILENAME));
|
|
|
|
try {
|
|
|
|
byte[] fileBytes = file.readFully();
|
2019-12-25 19:21:34 +01:00
|
|
|
_file = VaultFile.fromBytes(fileBytes);
|
2018-10-06 22:23:38 +02:00
|
|
|
_encrypt = _file.isEncrypted();
|
|
|
|
if (!isEncryptionEnabled()) {
|
2018-03-19 18:00:53 +01:00
|
|
|
JSONObject obj = _file.getContent();
|
2019-12-25 19:21:34 +01:00
|
|
|
_vault = Vault.fromJson(obj);
|
2018-03-19 18:00:53 +01:00
|
|
|
}
|
2019-12-25 19:21:34 +01:00
|
|
|
} catch (IOException | VaultFileException | VaultException e) {
|
|
|
|
throw new VaultManagerException(e);
|
2017-08-06 16:03:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-19 18:00:53 +01:00
|
|
|
public void lock() {
|
2017-12-24 22:29:32 +01:00
|
|
|
assertState(false, true);
|
2018-10-06 22:23:38 +02:00
|
|
|
_creds = null;
|
2019-12-25 19:21:34 +01:00
|
|
|
_vault = null;
|
2017-12-23 22:33:32 +01:00
|
|
|
}
|
|
|
|
|
2019-12-25 19:21:34 +01:00
|
|
|
public void unlock(VaultFileCredentials creds) throws VaultManagerException {
|
2017-12-24 22:29:32 +01:00
|
|
|
assertState(true, true);
|
2017-08-06 16:03:36 +02:00
|
|
|
|
2018-03-19 18:00:53 +01:00
|
|
|
try {
|
2018-10-06 22:23:38 +02:00
|
|
|
JSONObject obj = _file.getContent(creds);
|
2019-12-25 19:21:34 +01:00
|
|
|
_vault = Vault.fromJson(obj);
|
2018-10-06 22:23:38 +02:00
|
|
|
_creds = creds;
|
2019-06-12 01:49:26 +02:00
|
|
|
_context.startService(new Intent(_context, NotificationService.class));
|
2019-12-25 19:21:34 +01:00
|
|
|
} catch (VaultFileException | VaultException e) {
|
|
|
|
throw new VaultManagerException(e);
|
2018-03-19 18:00:53 +01:00
|
|
|
}
|
|
|
|
}
|
2017-12-10 19:19:48 +01:00
|
|
|
|
2020-02-02 13:23:45 +01:00
|
|
|
public static void save(Context context, VaultFile vaultFile) throws VaultManagerException {
|
|
|
|
byte[] bytes = vaultFile.toBytes();
|
|
|
|
AtomicFile file = new AtomicFile(new File(context.getFilesDir(), FILENAME));
|
|
|
|
|
|
|
|
FileOutputStream stream = null;
|
|
|
|
try {
|
|
|
|
stream = file.startWrite();
|
2018-10-06 22:23:38 +02:00
|
|
|
stream.write(bytes);
|
2020-02-02 13:23:45 +01:00
|
|
|
file.finishWrite(stream);
|
2018-06-06 16:15:31 +02:00
|
|
|
} catch (IOException e) {
|
2020-02-02 13:23:45 +01:00
|
|
|
if (stream != null) {
|
|
|
|
file.failWrite(stream);
|
|
|
|
}
|
2019-12-25 19:21:34 +01:00
|
|
|
throw new VaultManagerException(e);
|
2017-12-10 19:19:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-25 19:21:34 +01:00
|
|
|
public void save() throws VaultManagerException {
|
2017-12-24 22:29:32 +01:00
|
|
|
assertState(false, true);
|
2018-03-19 18:00:53 +01:00
|
|
|
|
|
|
|
try {
|
2019-12-25 19:21:34 +01:00
|
|
|
JSONObject obj = _vault.toJson();
|
2018-10-06 22:23:38 +02:00
|
|
|
if (isEncryptionEnabled()) {
|
|
|
|
_file.setContent(obj, _creds);
|
2018-03-19 18:00:53 +01:00
|
|
|
} else {
|
|
|
|
_file.setContent(obj);
|
|
|
|
}
|
|
|
|
save(_context, _file);
|
2020-01-04 22:06:59 +01:00
|
|
|
|
|
|
|
if (_prefs.isBackupsEnabled()) {
|
|
|
|
backup();
|
|
|
|
}
|
2019-12-25 19:21:34 +01:00
|
|
|
} catch (VaultFileException e) {
|
|
|
|
throw new VaultManagerException(e);
|
2017-12-10 19:19:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-04 18:40:25 +01:00
|
|
|
public void export(OutputStream stream, boolean encrypt) throws VaultManagerException {
|
2017-12-24 22:29:32 +01:00
|
|
|
assertState(false, true);
|
2018-03-13 18:30:47 +01:00
|
|
|
|
2017-12-10 19:19:48 +01:00
|
|
|
try {
|
2019-12-25 19:21:34 +01:00
|
|
|
VaultFile vaultFile = new VaultFile();
|
2018-10-06 22:23:38 +02:00
|
|
|
if (encrypt && isEncryptionEnabled()) {
|
2019-12-25 19:21:34 +01:00
|
|
|
vaultFile.setContent(_vault.toJson(), _creds);
|
2018-03-19 18:00:53 +01:00
|
|
|
} else {
|
2019-12-25 19:21:34 +01:00
|
|
|
vaultFile.setContent(_vault.toJson());
|
2017-12-10 19:19:48 +01:00
|
|
|
}
|
|
|
|
|
2019-12-25 19:21:34 +01:00
|
|
|
byte[] bytes = vaultFile.toBytes();
|
2020-01-04 18:40:25 +01:00
|
|
|
stream.write(bytes);
|
2019-12-25 19:21:34 +01:00
|
|
|
} catch (IOException | VaultFileException e) {
|
|
|
|
throw new VaultManagerException(e);
|
2018-03-19 18:00:53 +01:00
|
|
|
}
|
2017-08-06 16:03:36 +02:00
|
|
|
}
|
|
|
|
|
2020-01-04 22:06:59 +01:00
|
|
|
public void backup() throws VaultManagerException {
|
|
|
|
assertState(false, true);
|
|
|
|
_backups.create(_prefs.getBackupsLocation(), _prefs.getBackupsVersionCount());
|
|
|
|
}
|
|
|
|
|
2019-12-25 19:21:34 +01:00
|
|
|
public void addEntry(VaultEntry entry) {
|
2017-12-24 22:29:32 +01:00
|
|
|
assertState(false, true);
|
2019-12-25 19:21:34 +01:00
|
|
|
_vault.getEntries().add(entry);
|
2017-08-06 16:03:36 +02:00
|
|
|
}
|
|
|
|
|
2020-05-02 18:51:33 +02:00
|
|
|
public VaultEntry getEntryByUUID(UUID uuid) {
|
|
|
|
assertState(false, true);
|
|
|
|
return _vault.getEntries().getByUUID(uuid);
|
|
|
|
}
|
|
|
|
|
2019-12-25 19:21:34 +01:00
|
|
|
public VaultEntry removeEntry(VaultEntry entry) {
|
2017-12-24 22:29:32 +01:00
|
|
|
assertState(false, true);
|
2019-12-25 19:21:34 +01:00
|
|
|
return _vault.getEntries().remove(entry);
|
2017-08-06 16:03:36 +02:00
|
|
|
}
|
|
|
|
|
2019-12-25 19:21:34 +01:00
|
|
|
public VaultEntry replaceEntry(VaultEntry entry) {
|
2017-12-27 22:04:22 +01:00
|
|
|
assertState(false, true);
|
2019-12-25 19:21:34 +01:00
|
|
|
return _vault.getEntries().replace(entry);
|
2017-12-27 22:04:22 +01:00
|
|
|
}
|
|
|
|
|
2019-12-25 19:21:34 +01:00
|
|
|
public void swapEntries(VaultEntry entry1, VaultEntry entry2) {
|
2017-12-24 22:29:32 +01:00
|
|
|
assertState(false, true);
|
2019-12-25 19:21:34 +01:00
|
|
|
_vault.getEntries().swap(entry1, entry2);
|
2017-12-12 01:50:00 +01:00
|
|
|
}
|
|
|
|
|
2019-12-25 19:21:34 +01:00
|
|
|
public boolean isEntryDuplicate(VaultEntry entry) {
|
2017-12-24 22:29:32 +01:00
|
|
|
assertState(false, true);
|
2019-12-25 19:21:34 +01:00
|
|
|
return _vault.getEntries().has(entry);
|
2017-08-06 16:03:36 +02:00
|
|
|
}
|
|
|
|
|
2019-12-25 19:21:34 +01:00
|
|
|
public Collection<VaultEntry> getEntries() {
|
2018-09-22 14:12:42 +02:00
|
|
|
assertState(false, true);
|
2019-12-25 19:21:34 +01:00
|
|
|
return _vault.getEntries().getValues();
|
2018-09-22 14:12:42 +02:00
|
|
|
}
|
|
|
|
|
2018-12-11 11:44:36 +01:00
|
|
|
public TreeSet<String> getGroups() {
|
2018-12-18 22:46:35 +01:00
|
|
|
assertState(false, true);
|
|
|
|
|
2018-12-11 11:44:36 +01:00
|
|
|
TreeSet<String> groups = new TreeSet<>(Collator.getInstance());
|
2019-12-25 19:21:34 +01:00
|
|
|
for (VaultEntry entry : getEntries()) {
|
2018-12-11 11:44:36 +01:00
|
|
|
String group = entry.getGroup();
|
|
|
|
if (group != null) {
|
|
|
|
groups.add(group);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return groups;
|
|
|
|
}
|
|
|
|
|
2019-12-25 19:21:34 +01:00
|
|
|
public VaultFileCredentials getCredentials() {
|
2018-02-09 17:31:07 +01:00
|
|
|
assertState(false, true);
|
2018-10-06 22:23:38 +02:00
|
|
|
return _creds;
|
2018-02-09 17:31:07 +01:00
|
|
|
}
|
|
|
|
|
2019-12-25 19:21:34 +01:00
|
|
|
public void setCredentials(VaultFileCredentials creds) {
|
2018-10-06 22:23:38 +02:00
|
|
|
assertState(false, true);
|
|
|
|
_creds = creds;
|
2017-08-06 18:15:47 +02:00
|
|
|
}
|
|
|
|
|
2019-12-25 19:21:34 +01:00
|
|
|
public VaultFile.Header getFileHeader() {
|
2018-10-06 22:23:38 +02:00
|
|
|
assertLoaded(true);
|
|
|
|
return _file.getHeader();
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isEncryptionEnabled() {
|
|
|
|
assertLoaded(true);
|
|
|
|
return _encrypt;
|
|
|
|
}
|
|
|
|
|
2019-12-25 19:21:34 +01:00
|
|
|
public void enableEncryption(VaultFileCredentials creds) throws VaultManagerException {
|
2018-05-14 16:53:27 +02:00
|
|
|
assertState(false, true);
|
2018-10-06 22:23:38 +02:00
|
|
|
_creds = creds;
|
|
|
|
_encrypt = true;
|
|
|
|
save();
|
2018-05-14 16:53:27 +02:00
|
|
|
}
|
|
|
|
|
2019-12-25 19:21:34 +01:00
|
|
|
public void disableEncryption() throws VaultManagerException {
|
2018-05-14 16:53:27 +02:00
|
|
|
assertState(false, true);
|
2018-10-06 22:23:38 +02:00
|
|
|
_creds = null;
|
|
|
|
_encrypt = false;
|
|
|
|
save();
|
2018-05-14 16:53:27 +02:00
|
|
|
}
|
|
|
|
|
2017-08-06 16:03:36 +02:00
|
|
|
public boolean isLoaded() {
|
|
|
|
return _file != null;
|
|
|
|
}
|
|
|
|
|
2017-12-24 22:29:32 +01:00
|
|
|
public boolean isLocked() {
|
2019-12-25 19:21:34 +01:00
|
|
|
return _vault == null;
|
2017-08-06 16:03:36 +02:00
|
|
|
}
|
|
|
|
|
2018-03-19 18:00:53 +01:00
|
|
|
private void assertState(boolean locked, boolean loaded) {
|
2018-10-06 22:23:38 +02:00
|
|
|
assertLoaded(loaded);
|
2017-08-06 16:03:36 +02:00
|
|
|
|
2017-12-24 22:29:32 +01:00
|
|
|
if (isLocked() && !locked) {
|
2019-12-25 19:21:34 +01:00
|
|
|
throw new AssertionError("vault file has not been unlocked yet");
|
2017-12-24 22:29:32 +01:00
|
|
|
} else if (!isLocked() && locked) {
|
2019-12-25 19:21:34 +01:00
|
|
|
throw new AssertionError("vault file has already been unlocked");
|
2018-10-06 22:23:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void assertLoaded(boolean loaded) {
|
|
|
|
if (isLoaded() && !loaded) {
|
2019-12-25 19:21:34 +01:00
|
|
|
throw new AssertionError("vault file has already been loaded");
|
2018-10-06 22:23:38 +02:00
|
|
|
} else if (!isLoaded() && loaded) {
|
2019-12-25 19:21:34 +01:00
|
|
|
throw new AssertionError("vault file has not been loaded yet");
|
2017-08-06 16:03:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|