2017-08-06 16:03:36 +02:00
|
|
|
package me.impy.aegis.db;
|
|
|
|
|
|
|
|
import android.content.Context;
|
2017-12-10 19:19:48 +01:00
|
|
|
import android.os.Environment;
|
2017-08-06 16:03: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.DataInputStream;
|
|
|
|
import java.io.File;
|
|
|
|
import java.io.FileInputStream;
|
|
|
|
import java.io.FileOutputStream;
|
|
|
|
import java.io.IOException;
|
2017-08-06 16:03:36 +02:00
|
|
|
import java.util.List;
|
|
|
|
|
2017-12-12 12:25:55 +01:00
|
|
|
import me.impy.aegis.BuildConfig;
|
2017-08-06 16:03:36 +02:00
|
|
|
import me.impy.aegis.crypto.MasterKey;
|
|
|
|
|
|
|
|
public class DatabaseManager {
|
2018-02-13 22:06:24 +01:00
|
|
|
private static final String FILENAME = "aegis.json";
|
|
|
|
private static final String FILENAME_EXPORT = "aegis_export.json";
|
|
|
|
private static final String FILENAME_EXPORT_PLAIN = "aegis_export_plain.json";
|
2017-12-04 22:08:50 +01:00
|
|
|
|
2017-08-06 16:03:36 +02:00
|
|
|
private MasterKey _key;
|
|
|
|
private DatabaseFile _file;
|
|
|
|
private Database _db;
|
|
|
|
private Context _context;
|
|
|
|
|
|
|
|
public DatabaseManager(Context context) {
|
|
|
|
_context = context;
|
|
|
|
}
|
|
|
|
|
2017-12-25 00:16:24 +01:00
|
|
|
public boolean fileExists() {
|
|
|
|
File file = new File(_context.getFilesDir(), FILENAME);
|
|
|
|
return file.exists() && file.isFile();
|
|
|
|
}
|
|
|
|
|
2018-03-19 18:00:53 +01:00
|
|
|
public void load() throws DatabaseManagerException {
|
2017-12-24 22:29:32 +01:00
|
|
|
assertState(true, false);
|
|
|
|
|
2017-12-10 19:19:48 +01:00
|
|
|
try {
|
2018-03-19 18:00:53 +01:00
|
|
|
byte[] fileBytes;
|
|
|
|
FileInputStream file = null;
|
|
|
|
|
|
|
|
try {
|
|
|
|
file = _context.openFileInput(FILENAME);
|
|
|
|
fileBytes = new byte[(int) file.getChannel().size()];
|
|
|
|
DataInputStream stream = new DataInputStream(file);
|
|
|
|
stream.readFully(fileBytes);
|
|
|
|
stream.close();
|
|
|
|
} finally {
|
|
|
|
// always close the file stream
|
|
|
|
// there is no need to close the DataInputStream
|
|
|
|
if (file != null) {
|
|
|
|
file.close();
|
|
|
|
}
|
2017-12-10 19:19:48 +01:00
|
|
|
}
|
|
|
|
|
2018-03-19 18:00:53 +01:00
|
|
|
_file = new DatabaseFile();
|
|
|
|
_file.deserialize(fileBytes);
|
2017-12-10 19:19:48 +01:00
|
|
|
|
2018-03-19 18:00:53 +01:00
|
|
|
if (!_file.isEncrypted()) {
|
|
|
|
JSONObject obj = _file.getContent();
|
|
|
|
_db = new Database();
|
|
|
|
_db.deserialize(obj);
|
|
|
|
}
|
|
|
|
} catch (IOException | DatabaseFileException | DatabaseException e) {
|
|
|
|
throw new DatabaseManagerException(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);
|
2017-12-23 22:33:32 +01:00
|
|
|
// TODO: properly clear everything
|
|
|
|
_key = null;
|
|
|
|
_db = null;
|
|
|
|
}
|
|
|
|
|
2018-03-19 18:00:53 +01:00
|
|
|
public void unlock(MasterKey key) throws DatabaseManagerException {
|
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 {
|
|
|
|
JSONObject obj = _file.getContent(key);
|
|
|
|
_db = new Database();
|
|
|
|
_db.deserialize(obj);
|
|
|
|
_key = key;
|
|
|
|
} catch (DatabaseFileException | DatabaseException e) {
|
|
|
|
throw new DatabaseManagerException(e);
|
|
|
|
}
|
|
|
|
}
|
2017-12-10 19:19:48 +01:00
|
|
|
|
2018-03-19 18:00:53 +01:00
|
|
|
public static void save(Context context, DatabaseFile file) throws DatabaseManagerException {
|
2017-12-10 19:19:48 +01:00
|
|
|
try {
|
2018-03-19 18:00:53 +01:00
|
|
|
byte[] bytes = file.serialize();
|
|
|
|
|
|
|
|
FileOutputStream stream = null;
|
|
|
|
try {
|
|
|
|
stream = context.openFileOutput(FILENAME, Context.MODE_PRIVATE);
|
|
|
|
stream.write(bytes);
|
|
|
|
} finally {
|
|
|
|
// always close the file stream
|
|
|
|
if (stream != null) {
|
|
|
|
stream.close();
|
|
|
|
}
|
2017-12-10 19:19:48 +01:00
|
|
|
}
|
2018-03-19 18:00:53 +01:00
|
|
|
} catch (IOException | DatabaseFileException e) {
|
|
|
|
throw new DatabaseManagerException(e);
|
2017-12-10 19:19:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-19 18:00:53 +01:00
|
|
|
public void save() throws DatabaseManagerException {
|
2017-12-24 22:29:32 +01:00
|
|
|
assertState(false, true);
|
2018-03-19 18:00:53 +01:00
|
|
|
|
|
|
|
try {
|
|
|
|
JSONObject obj = _db.serialize();
|
|
|
|
if (_file.isEncrypted()) {
|
|
|
|
_file.setContent(obj, _key);
|
|
|
|
} else {
|
|
|
|
_file.setContent(obj);
|
|
|
|
}
|
|
|
|
save(_context, _file);
|
|
|
|
} catch (DatabaseException | DatabaseFileException e) {
|
|
|
|
throw new DatabaseManagerException(e);
|
2017-12-10 19:19:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-19 18:00:53 +01:00
|
|
|
public String export(boolean encrypt) throws DatabaseManagerException {
|
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 {
|
2018-03-19 18:00:53 +01:00
|
|
|
DatabaseFile dbFile = new DatabaseFile();
|
|
|
|
dbFile.setSlots(_file.getSlots());
|
|
|
|
if (encrypt && getFile().isEncrypted()) {
|
|
|
|
dbFile.setContent(_db.serialize(), _key);
|
|
|
|
} else {
|
|
|
|
dbFile.setContent(_db.serialize());
|
2017-12-10 19:19:48 +01:00
|
|
|
}
|
|
|
|
|
2018-03-19 18:00:53 +01:00
|
|
|
File file;
|
|
|
|
FileOutputStream stream = null;
|
|
|
|
try {
|
|
|
|
String dirName = !BuildConfig.DEBUG ? "Aegis" : "AegisDebug";
|
|
|
|
File dir = new File(Environment.getExternalStorageDirectory(), dirName);
|
|
|
|
if (!dir.exists() && !dir.mkdirs()) {
|
|
|
|
throw new IOException("error creating external storage directory");
|
|
|
|
}
|
|
|
|
|
|
|
|
byte[] bytes = dbFile.serialize();
|
|
|
|
file = new File(dir.getAbsolutePath(), encrypt ? FILENAME_EXPORT : FILENAME_EXPORT_PLAIN);
|
|
|
|
stream = new FileOutputStream(file);
|
|
|
|
stream.write(bytes);
|
|
|
|
} finally {
|
|
|
|
// always close the file stream
|
|
|
|
if (stream != null) {
|
|
|
|
stream.close();
|
|
|
|
}
|
2017-12-10 19:19:48 +01:00
|
|
|
}
|
|
|
|
|
2018-03-19 18:00:53 +01:00
|
|
|
return file.getAbsolutePath();
|
|
|
|
} catch (DatabaseException | IOException | DatabaseFileException e) {
|
|
|
|
throw new DatabaseManagerException(e);
|
|
|
|
}
|
2017-08-06 16:03:36 +02:00
|
|
|
}
|
|
|
|
|
2018-03-19 18:00:53 +01:00
|
|
|
public void addKey(DatabaseEntry entry) {
|
2017-12-24 22:29:32 +01:00
|
|
|
assertState(false, true);
|
2017-08-26 21:15:53 +02:00
|
|
|
_db.addKey(entry);
|
2017-08-06 16:03:36 +02:00
|
|
|
}
|
|
|
|
|
2018-03-19 18:00:53 +01:00
|
|
|
public void removeKey(DatabaseEntry entry) {
|
2017-12-24 22:29:32 +01:00
|
|
|
assertState(false, true);
|
2017-08-26 21:15:53 +02:00
|
|
|
_db.removeKey(entry);
|
2017-08-06 16:03:36 +02:00
|
|
|
}
|
|
|
|
|
2018-03-19 18:00:53 +01:00
|
|
|
public void replaceKey(DatabaseEntry entry) {
|
2017-12-27 22:04:22 +01:00
|
|
|
assertState(false, true);
|
|
|
|
_db.replaceKey(entry);
|
|
|
|
}
|
|
|
|
|
2018-03-19 18:00:53 +01:00
|
|
|
public void swapKeys(DatabaseEntry entry1, DatabaseEntry entry2) {
|
2017-12-24 22:29:32 +01:00
|
|
|
assertState(false, true);
|
2017-12-12 01:50:00 +01:00
|
|
|
_db.swapKeys(entry1, entry2);
|
|
|
|
}
|
|
|
|
|
2018-03-19 18:00:53 +01:00
|
|
|
public List<DatabaseEntry> getKeys() {
|
2017-12-24 22:29:32 +01:00
|
|
|
assertState(false, true);
|
2017-08-06 16:03:36 +02:00
|
|
|
return _db.getKeys();
|
|
|
|
}
|
|
|
|
|
2018-03-19 18:00:53 +01:00
|
|
|
public MasterKey getMasterKey() {
|
2018-02-09 17:31:07 +01:00
|
|
|
assertState(false, true);
|
|
|
|
return _key;
|
|
|
|
}
|
|
|
|
|
2017-08-06 18:15:47 +02:00
|
|
|
public DatabaseFile getFile() {
|
|
|
|
return _file;
|
|
|
|
}
|
|
|
|
|
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() {
|
|
|
|
return _db == null;
|
2017-08-06 16:03:36 +02:00
|
|
|
}
|
|
|
|
|
2018-03-19 18:00:53 +01:00
|
|
|
private void assertState(boolean locked, boolean loaded) {
|
2017-12-24 22:29:32 +01:00
|
|
|
if (isLoaded() && !loaded) {
|
2018-03-19 18:00:53 +01:00
|
|
|
throw new AssertionError("database file has not been loaded yet");
|
2017-12-24 22:29:32 +01:00
|
|
|
} else if (!isLoaded() && loaded) {
|
2018-03-19 18:00:53 +01:00
|
|
|
throw new AssertionError("database file has is already been loaded");
|
2017-08-06 16:03:36 +02:00
|
|
|
}
|
|
|
|
|
2017-12-24 22:29:32 +01:00
|
|
|
if (isLocked() && !locked) {
|
2018-03-19 18:00:53 +01:00
|
|
|
throw new AssertionError("database file has not been unlocked yet");
|
2017-12-24 22:29:32 +01:00
|
|
|
} else if (!isLocked() && locked) {
|
2018-03-19 18:00:53 +01:00
|
|
|
throw new AssertionError("database file has is already been unlocked");
|
2017-08-06 16:03:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|