Refactor the database classes to be more reusable

This commit is contained in:
Alexander Bakker 2018-10-06 22:23:38 +02:00
parent 571cf20eda
commit 0434513820
18 changed files with 311 additions and 256 deletions

View file

@ -30,7 +30,7 @@ public class CryptParameters implements Serializable {
return obj; return obj;
} }
public static CryptParameters parseJson(JSONObject obj) throws JSONException, HexException { public static CryptParameters fromJson(JSONObject obj) throws JSONException, HexException {
byte[] nonce = Hex.decode(obj.getString("nonce")); byte[] nonce = Hex.decode(obj.getString("nonce"));
byte[] tag = Hex.decode(obj.getString("tag")); byte[] tag = Hex.decode(obj.getString("tag"));
return new CryptParameters(nonce, tag); return new CryptParameters(nonce, tag);

View file

@ -12,14 +12,13 @@ import me.impy.aegis.otp.OtpInfoException;
public class Database { public class Database {
private static final int VERSION = 1; private static final int VERSION = 1;
private DatabaseEntryList _entries = new DatabaseEntryList(); private DatabaseEntryList _entries = new DatabaseEntryList();
public JSONObject serialize() { public JSONObject toJson() {
try { try {
JSONArray array = new JSONArray(); JSONArray array = new JSONArray();
for (DatabaseEntry e : _entries) { for (DatabaseEntry e : _entries) {
array.put(e.serialize()); array.put(e.toJson());
} }
JSONObject obj = new JSONObject(); JSONObject obj = new JSONObject();
@ -31,8 +30,9 @@ public class Database {
} }
} }
public void deserialize(JSONObject obj) throws DatabaseException { public static Database fromJson(JSONObject obj) throws DatabaseException {
// TODO: support different VERSION deserialization providers Database db = new Database();
try { try {
int ver = obj.getInt("version"); int ver = obj.getInt("version");
if (ver != VERSION) { if (ver != VERSION) {
@ -41,13 +41,14 @@ public class Database {
JSONArray array = obj.getJSONArray("entries"); JSONArray array = obj.getJSONArray("entries");
for (int i = 0; i < array.length(); i++) { for (int i = 0; i < array.length(); i++) {
DatabaseEntry entry = new DatabaseEntry(null); DatabaseEntry entry = DatabaseEntry.fromJson(array.getJSONObject(i));
entry.deserialize(array.getJSONObject(i)); db.addEntry(entry);
addEntry(entry);
} }
} catch (Base64Exception | OtpInfoException | JSONException e) { } catch (Base64Exception | OtpInfoException | JSONException e) {
throw new DatabaseException(e); throw new DatabaseException(e);
} }
return db;
} }
public void addEntry(DatabaseEntry entry) { public void addEntry(DatabaseEntry entry) {

View file

@ -9,6 +9,7 @@ import java.util.UUID;
import me.impy.aegis.encoding.Base64; import me.impy.aegis.encoding.Base64;
import me.impy.aegis.encoding.Base64Exception; import me.impy.aegis.encoding.Base64Exception;
import me.impy.aegis.otp.GoogleAuthInfo;
import me.impy.aegis.otp.OtpInfo; import me.impy.aegis.otp.OtpInfo;
import me.impy.aegis.otp.OtpInfoException; import me.impy.aegis.otp.OtpInfoException;
@ -19,9 +20,13 @@ public class DatabaseEntry implements Serializable {
private OtpInfo _info; private OtpInfo _info;
private byte[] _icon; private byte[] _icon;
public DatabaseEntry(OtpInfo info) { private DatabaseEntry(UUID uuid, OtpInfo info) {
_uuid = uuid;
_info = info; _info = info;
_uuid = UUID.randomUUID(); }
public DatabaseEntry(OtpInfo info) {
this(UUID.randomUUID(), info);
} }
public DatabaseEntry(OtpInfo info, String name, String issuer) { public DatabaseEntry(OtpInfo info, String name, String issuer) {
@ -30,7 +35,11 @@ public class DatabaseEntry implements Serializable {
setIssuer(issuer); setIssuer(issuer);
} }
public JSONObject serialize() { public DatabaseEntry(GoogleAuthInfo info) {
this(info.getOtpInfo(), info.getAccountName(), info.getIssuer());
}
public JSONObject toJson() {
JSONObject obj = new JSONObject(); JSONObject obj = new JSONObject();
try { try {
@ -47,22 +56,26 @@ public class DatabaseEntry implements Serializable {
return obj; return obj;
} }
public void deserialize(JSONObject obj) throws JSONException, OtpInfoException, Base64Exception { public static DatabaseEntry fromJson(JSONObject obj) throws JSONException, OtpInfoException, Base64Exception {
// if there is no uuid, generate a new one // if there is no uuid, generate a new one
UUID uuid;
if (!obj.has("uuid")) { if (!obj.has("uuid")) {
_uuid = UUID.randomUUID(); uuid = UUID.randomUUID();
} else { } else {
_uuid = UUID.fromString(obj.getString("uuid")); uuid = UUID.fromString(obj.getString("uuid"));
} }
_name = obj.getString("name");
_issuer = obj.getString("issuer"); OtpInfo info = OtpInfo.fromJson(obj.getString("type"), obj.getJSONObject("info"));
DatabaseEntry entry = new DatabaseEntry(uuid, info);
entry.setName(obj.getString("name"));
entry.setIssuer(obj.getString("issuer"));
Object icon = obj.get("icon"); Object icon = obj.get("icon");
if (icon != JSONObject.NULL) { if (icon != JSONObject.NULL) {
_icon = Base64.decode((String) icon); entry.setIcon(Base64.decode((String) icon));
} }
_info = OtpInfo.parseJson(obj.getString("type"), obj.getJSONObject("info")); return entry;
} }
public void resetUUID() { public void resetUUID() {

View file

@ -1,6 +1,5 @@
package me.impy.aegis.db; package me.impy.aegis.db;
import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@ -8,7 +7,6 @@ import java.io.UnsupportedEncodingException;
import me.impy.aegis.crypto.CryptParameters; import me.impy.aegis.crypto.CryptParameters;
import me.impy.aegis.crypto.CryptResult; import me.impy.aegis.crypto.CryptResult;
import me.impy.aegis.crypto.MasterKey;
import me.impy.aegis.crypto.MasterKeyException; import me.impy.aegis.crypto.MasterKeyException;
import me.impy.aegis.db.slots.SlotList; import me.impy.aegis.db.slots.SlotList;
import me.impy.aegis.db.slots.SlotListException; import me.impy.aegis.db.slots.SlotListException;
@ -20,22 +18,41 @@ public class DatabaseFile {
public static final byte VERSION = 1; public static final byte VERSION = 1;
private Object _content; private Object _content;
private CryptParameters _cryptParameters; private Header _header;
private SlotList _slots;
public byte[] serialize() { public DatabaseFile() {
}
private DatabaseFile(Object content, Header header) {
_content = content;
_header = header;
}
public Header getHeader() {
return _header;
}
public boolean isEncrypted() {
return !_header.isEmpty();
}
public JSONObject toJson() {
try { try {
// don't write the crypt parameters and slots if the content is not encrypted
boolean plain = _content instanceof JSONObject || !isEncrypted();
JSONObject headerObj = new JSONObject();
headerObj.put("slots", plain ? JSONObject.NULL : SlotList.serialize(_slots));
headerObj.put("params", plain ? JSONObject.NULL : _cryptParameters.toJson());
JSONObject obj = new JSONObject(); JSONObject obj = new JSONObject();
obj.put("version", VERSION); obj.put("version", VERSION);
obj.put("header", headerObj); obj.put("header", _header.toJson());
obj.put("db", _content); obj.put("db", _content);
return obj;
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
public byte[] toBytes() {
JSONObject obj = toJson();
try {
String string = obj.toString(4); String string = obj.toString(4);
return string.getBytes("UTF-8"); return string.getBytes("UTF-8");
} catch (JSONException | UnsupportedEncodingException e) { } catch (JSONException | UnsupportedEncodingException e) {
@ -43,76 +60,108 @@ public class DatabaseFile {
} }
} }
public void deserialize(byte[] data) throws DatabaseFileException { public static DatabaseFile fromJson(JSONObject obj) throws DatabaseFileException {
try { try {
JSONObject obj = new JSONObject(new String(data, "UTF-8"));
JSONObject headerObj = obj.getJSONObject("header");
if (obj.getInt("version") > VERSION) { if (obj.getInt("version") > VERSION) {
throw new DatabaseFileException("unsupported version"); throw new DatabaseFileException("unsupported version");
} }
JSONArray slotObj = headerObj.optJSONArray("slots"); Header header = Header.fromJson(obj.getJSONObject("header"));
if (slotObj != null) { if (!header.isEmpty()) {
_slots = SlotList.deserialize(slotObj); return new DatabaseFile(obj.getString("db"), header);
} }
JSONObject cryptObj = headerObj.optJSONObject("params"); return new DatabaseFile(obj.getJSONObject("db"), header);
if (cryptObj != null) { } catch (JSONException e) {
_cryptParameters = CryptParameters.parseJson(cryptObj);
}
if (cryptObj == null || slotObj == null) {
_content = obj.getJSONObject("db");
} else {
_content = obj.getString("db");
}
} catch (SlotListException | UnsupportedEncodingException | JSONException | HexException e) {
throw new DatabaseFileException(e); throw new DatabaseFileException(e);
} }
} }
public boolean isEncrypted() { public static DatabaseFile fromBytes(byte[] data) throws DatabaseFileException {
return _slots != null; try {
JSONObject obj = new JSONObject(new String(data, "UTF-8"));
return DatabaseFile.fromJson(obj);
} catch (UnsupportedEncodingException | JSONException e) {
throw new DatabaseFileException(e);
}
} }
public JSONObject getContent() { public JSONObject getContent() {
return (JSONObject) _content; return (JSONObject) _content;
} }
public JSONObject getContent(MasterKey key) throws DatabaseFileException { public JSONObject getContent(DatabaseFileCredentials creds) throws DatabaseFileException {
try { try {
byte[] bytes = Base64.decode((String) _content); byte[] bytes = Base64.decode((String) _content);
CryptResult result = key.decrypt(bytes, _cryptParameters); CryptResult result = creds.decrypt(bytes, _header.getParams());
return new JSONObject(new String(result.getData(), "UTF-8")); return new JSONObject(new String(result.getData(), "UTF-8"));
} catch (MasterKeyException | JSONException | UnsupportedEncodingException | Base64Exception e) { } catch (MasterKeyException | JSONException | UnsupportedEncodingException | Base64Exception e) {
throw new DatabaseFileException(e); throw new DatabaseFileException(e);
} }
} }
public void setContent(JSONObject dbObj) { public void setContent(JSONObject obj) {
_content = dbObj; _content = obj;
_cryptParameters = null; _header = new Header(null, null);
_slots = null;
} }
public void setContent(JSONObject dbObj, MasterKey key) throws DatabaseFileException { public void setContent(JSONObject obj, DatabaseFileCredentials creds) throws DatabaseFileException {
try { try {
String string = dbObj.toString(4); String string = obj.toString(4);
byte[] dbBytes = string.getBytes("UTF-8"); byte[] dbBytes = string.getBytes("UTF-8");
CryptResult result = key.encrypt(dbBytes); CryptResult result = creds.encrypt(dbBytes);
_content = Base64.encode(result.getData()); _content = Base64.encode(result.getData());
_cryptParameters = result.getParams(); _header = new Header(creds.getSlots(), result.getParams());
} catch (MasterKeyException | UnsupportedEncodingException | JSONException e) { } catch (MasterKeyException | UnsupportedEncodingException | JSONException e) {
throw new DatabaseFileException(e); throw new DatabaseFileException(e);
} }
} }
public SlotList getSlots() { public static class Header {
return _slots; private SlotList _slots;
} private CryptParameters _params;
public void setSlots(SlotList slots) { public Header(SlotList slots, CryptParameters params) {
_slots = slots; _slots = slots;
_params = params;
}
public static Header fromJson(JSONObject obj) throws DatabaseFileException {
if (obj.isNull("slots") && obj.isNull("params")) {
return new Header(null, null);
}
try {
SlotList slots = SlotList.fromJson(obj.getJSONArray("slots"));
CryptParameters params = CryptParameters.fromJson(obj.getJSONObject("params"));
return new Header(slots, params);
} catch (SlotListException | JSONException | HexException e) {
throw new DatabaseFileException(e);
}
}
public JSONObject toJson() {
try {
JSONObject obj = new JSONObject();
obj.put("slots", _slots != null ? _slots.toJson() : JSONObject.NULL);
obj.put("params", _params != null ? _params.toJson() : JSONObject.NULL);
return obj;
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
public SlotList getSlots() {
return _slots;
}
public CryptParameters getParams() {
return _params;
}
public boolean isEmpty() {
return _slots == null && _params == null;
}
} }
} }

View file

@ -0,0 +1,40 @@
package me.impy.aegis.db;
import java.io.Serializable;
import me.impy.aegis.crypto.CryptParameters;
import me.impy.aegis.crypto.CryptResult;
import me.impy.aegis.crypto.MasterKey;
import me.impy.aegis.crypto.MasterKeyException;
import me.impy.aegis.db.slots.SlotList;
public class DatabaseFileCredentials implements Serializable {
private MasterKey _key;
private SlotList _slots;
public DatabaseFileCredentials() {
_key = MasterKey.generate();
_slots = new SlotList();
}
public DatabaseFileCredentials(MasterKey key, SlotList slots) {
_key = key;
_slots = slots;
}
public CryptResult encrypt(byte[] bytes) throws MasterKeyException {
return _key.encrypt(bytes);
}
public CryptResult decrypt(byte[] bytes, CryptParameters params) throws MasterKeyException {
return _key.decrypt(bytes, params);
}
public MasterKey getKey() {
return _key;
}
public SlotList getSlots() {
return _slots;
}
}

View file

@ -14,17 +14,17 @@ import java.util.List;
import java.util.UUID; import java.util.UUID;
import me.impy.aegis.BuildConfig; import me.impy.aegis.BuildConfig;
import me.impy.aegis.crypto.MasterKey;
import me.impy.aegis.db.slots.SlotList;
public class DatabaseManager { public class DatabaseManager {
private static final String FILENAME = "aegis.json"; private static final String FILENAME = "aegis.json";
private static final String FILENAME_EXPORT = "aegis_export.json"; private static final String FILENAME_EXPORT = "aegis_export.json";
private static final String FILENAME_EXPORT_PLAIN = "aegis_export_plain.json"; private static final String FILENAME_EXPORT_PLAIN = "aegis_export_plain.json";
private MasterKey _key;
private DatabaseFile _file;
private Database _db; private Database _db;
private DatabaseFile _file;
private DatabaseFileCredentials _creds;
private boolean _encrypt;
private Context _context; private Context _context;
public DatabaseManager(Context context) { public DatabaseManager(Context context) {
@ -39,31 +39,17 @@ public class DatabaseManager {
public void load() throws DatabaseManagerException { public void load() throws DatabaseManagerException {
assertState(true, false); assertState(true, false);
try { try (FileInputStream file = _context.openFileInput(FILENAME)) {
byte[] fileBytes; byte[] fileBytes = new byte[(int) file.getChannel().size()];
FileInputStream file = null; DataInputStream stream = new DataInputStream(file);
stream.readFully(fileBytes);
stream.close();
try { _file = DatabaseFile.fromBytes(fileBytes);
file = _context.openFileInput(FILENAME); _encrypt = _file.isEncrypted();
fileBytes = new byte[(int) file.getChannel().size()]; if (!isEncryptionEnabled()) {
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();
}
}
_file = new DatabaseFile();
_file.deserialize(fileBytes);
if (!_file.isEncrypted()) {
JSONObject obj = _file.getContent(); JSONObject obj = _file.getContent();
_db = new Database(); _db = Database.fromJson(obj);
_db.deserialize(obj);
} }
} catch (IOException | DatabaseFileException | DatabaseException e) { } catch (IOException | DatabaseFileException | DatabaseException e) {
throw new DatabaseManagerException(e); throw new DatabaseManagerException(e);
@ -73,37 +59,26 @@ public class DatabaseManager {
public void lock() { public void lock() {
assertState(false, true); assertState(false, true);
// TODO: properly clear everything // TODO: properly clear everything
_key = null; _creds = null;
_db = null; _db = null;
} }
public void unlock(MasterKey key) throws DatabaseManagerException { public void unlock(DatabaseFileCredentials creds) throws DatabaseManagerException {
assertState(true, true); assertState(true, true);
try { try {
JSONObject obj = _file.getContent(key); JSONObject obj = _file.getContent(creds);
_db = new Database(); _db = Database.fromJson(obj);
_db.deserialize(obj); _creds = creds;
_key = key;
} catch (DatabaseFileException | DatabaseException e) { } catch (DatabaseFileException | DatabaseException e) {
throw new DatabaseManagerException(e); throw new DatabaseManagerException(e);
} }
} }
public static void save(Context context, DatabaseFile file) throws DatabaseManagerException { public static void save(Context context, DatabaseFile file) throws DatabaseManagerException {
try { byte[] bytes = file.toBytes();
byte[] bytes = file.serialize(); try (FileOutputStream stream = context.openFileOutput(FILENAME, Context.MODE_PRIVATE)) {
stream.write(bytes);
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();
}
}
} catch (IOException e) { } catch (IOException e) {
throw new DatabaseManagerException(e); throw new DatabaseManagerException(e);
} }
@ -113,9 +88,9 @@ public class DatabaseManager {
assertState(false, true); assertState(false, true);
try { try {
JSONObject obj = _db.serialize(); JSONObject obj = _db.toJson();
if (_file.isEncrypted()) { if (isEncryptionEnabled()) {
_file.setContent(obj, _key); _file.setContent(obj, _creds);
} else { } else {
_file.setContent(obj); _file.setContent(obj);
} }
@ -130,31 +105,22 @@ public class DatabaseManager {
try { try {
DatabaseFile dbFile = new DatabaseFile(); DatabaseFile dbFile = new DatabaseFile();
dbFile.setSlots(_file.getSlots()); if (encrypt && isEncryptionEnabled()) {
if (encrypt && getFile().isEncrypted()) { dbFile.setContent(_db.toJson(), _creds);
dbFile.setContent(_db.serialize(), _key);
} else { } else {
dbFile.setContent(_db.serialize()); dbFile.setContent(_db.toJson());
} }
File file; String dirName = !BuildConfig.DEBUG ? "Aegis" : "AegisDebug";
FileOutputStream stream = null; File dir = new File(Environment.getExternalStorageDirectory(), dirName);
try { if (!dir.exists() && !dir.mkdirs()) {
String dirName = !BuildConfig.DEBUG ? "Aegis" : "AegisDebug"; throw new IOException("error creating external storage directory");
File dir = new File(Environment.getExternalStorageDirectory(), dirName); }
if (!dir.exists() && !dir.mkdirs()) {
throw new IOException("error creating external storage directory");
}
byte[] bytes = dbFile.serialize(); byte[] bytes = dbFile.toBytes();
file = new File(dir.getAbsolutePath(), encrypt ? FILENAME_EXPORT : FILENAME_EXPORT_PLAIN); File file = new File(dir.getAbsolutePath(), encrypt ? FILENAME_EXPORT : FILENAME_EXPORT_PLAIN);
stream = new FileOutputStream(file); try (FileOutputStream stream = new FileOutputStream(file)) {
stream.write(bytes); stream.write(bytes);
} finally {
// always close the file stream
if (stream != null) {
stream.close();
}
} }
return file.getAbsolutePath(); return file.getAbsolutePath();
@ -193,25 +159,38 @@ public class DatabaseManager {
return _db.getEntryByUUID(uuid); return _db.getEntryByUUID(uuid);
} }
public MasterKey getMasterKey() { public DatabaseFileCredentials getCredentials() {
assertState(false, true); assertState(false, true);
return _key; return _creds;
} }
public DatabaseFile getFile() { public void setCredentials(DatabaseFileCredentials creds) {
return _file; assertState(false, true);
_creds = creds;
} }
public void enableEncryption(MasterKey key, SlotList slots) { public DatabaseFile.Header getFileHeader() {
assertState(false, true); assertLoaded(true);
_key = key; return _file.getHeader();
_file.setSlots(slots);
} }
public void disableEncryption() { public boolean isEncryptionEnabled() {
assertLoaded(true);
return _encrypt;
}
public void enableEncryption(DatabaseFileCredentials creds) throws DatabaseManagerException {
assertState(false, true); assertState(false, true);
_key = null; _creds = creds;
_file.setSlots(null); _encrypt = true;
save();
}
public void disableEncryption() throws DatabaseManagerException {
assertState(false, true);
_creds = null;
_encrypt = false;
save();
} }
public boolean isLoaded() { public boolean isLoaded() {
@ -223,16 +202,20 @@ public class DatabaseManager {
} }
private void assertState(boolean locked, boolean loaded) { private void assertState(boolean locked, boolean loaded) {
if (isLoaded() && !loaded) { assertLoaded(loaded);
throw new AssertionError("database file has not been loaded yet");
} else if (!isLoaded() && loaded) {
throw new AssertionError("database file has is already been loaded");
}
if (isLocked() && !locked) { if (isLocked() && !locked) {
throw new AssertionError("database file has not been unlocked yet"); throw new AssertionError("database file has not been unlocked yet");
} else if (!isLocked() && locked) { } else if (!isLocked() && locked) {
throw new AssertionError("database file has is already been unlocked"); throw new AssertionError("database file has already been unlocked");
}
}
private void assertLoaded(boolean loaded) {
if (isLoaded() && !loaded) {
throw new AssertionError("database file has already been loaded");
} else if (!isLoaded() && loaded) {
throw new AssertionError("database file has not been loaded yet");
} }
} }
} }

View file

@ -20,9 +20,9 @@ public class PasswordSlot extends RawSlot {
} }
@Override @Override
public JSONObject serialize() { public JSONObject toJson() {
try { try {
JSONObject obj = super.serialize(); JSONObject obj = super.toJson();
obj.put("n", _n); obj.put("n", _n);
obj.put("r", _r); obj.put("r", _r);
obj.put("p", _p); obj.put("p", _p);

View file

@ -10,7 +10,6 @@ import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.UUID; import java.util.UUID;
import javax.crypto.AEADBadTagException;
import javax.crypto.BadPaddingException; import javax.crypto.BadPaddingException;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException; import javax.crypto.IllegalBlockSizeException;
@ -85,7 +84,7 @@ public abstract class Slot implements Serializable {
} }
} }
public JSONObject serialize() { public JSONObject toJson() {
try { try {
JSONObject obj = new JSONObject(); JSONObject obj = new JSONObject();
JSONObject paramObj = _encryptedMasterKeyParams.toJson(); JSONObject paramObj = _encryptedMasterKeyParams.toJson();
@ -114,7 +113,7 @@ public abstract class Slot implements Serializable {
JSONObject paramObj = obj.getJSONObject("key_params"); JSONObject paramObj = obj.getJSONObject("key_params");
_encryptedMasterKey = Hex.decode(obj.getString("key")); _encryptedMasterKey = Hex.decode(obj.getString("key"));
_encryptedMasterKeyParams = CryptParameters.parseJson(paramObj); _encryptedMasterKeyParams = CryptParameters.fromJson(paramObj);
} catch (JSONException | HexException e) { } catch (JSONException | HexException e) {
throw new SlotException(e); throw new SlotException(e);
} }

View file

@ -12,16 +12,16 @@ import java.util.List;
public class SlotList implements Iterable<Slot>, Serializable { public class SlotList implements Iterable<Slot>, Serializable {
private List<Slot> _slots = new ArrayList<>(); private List<Slot> _slots = new ArrayList<>();
public static JSONArray serialize(SlotList slots) { public JSONArray toJson() {
JSONArray array = new JSONArray(); JSONArray array = new JSONArray();
for (Slot slot : slots) { for (Slot slot : this) {
array.put(slot.serialize()); array.put(slot.toJson());
} }
return array; return array;
} }
public static SlotList deserialize(JSONArray array) throws SlotListException { public static SlotList fromJson(JSONArray array) throws SlotListException {
SlotList slots = new SlotList(); SlotList slots = new SlotList();
try { try {

View file

@ -4,16 +4,16 @@ import org.json.JSONObject;
import java.util.List; import java.util.List;
import me.impy.aegis.crypto.MasterKey;
import me.impy.aegis.db.Database; import me.impy.aegis.db.Database;
import me.impy.aegis.db.DatabaseEntry; import me.impy.aegis.db.DatabaseEntry;
import me.impy.aegis.db.DatabaseException; import me.impy.aegis.db.DatabaseException;
import me.impy.aegis.db.DatabaseFile; import me.impy.aegis.db.DatabaseFile;
import me.impy.aegis.db.DatabaseFileCredentials;
import me.impy.aegis.db.DatabaseFileException; import me.impy.aegis.db.DatabaseFileException;
import me.impy.aegis.util.ByteInputStream; import me.impy.aegis.util.ByteInputStream;
public class AegisImporter extends DatabaseImporter { public class AegisImporter extends DatabaseImporter {
private MasterKey _key; private DatabaseFileCredentials _creds;
private DatabaseFile _file; private DatabaseFile _file;
public AegisImporter(ByteInputStream stream) { public AegisImporter(ByteInputStream stream) {
@ -24,8 +24,7 @@ public class AegisImporter extends DatabaseImporter {
public void parse() throws DatabaseImporterException { public void parse() throws DatabaseImporterException {
try { try {
byte[] bytes = _stream.getBytes(); byte[] bytes = _stream.getBytes();
_file = new DatabaseFile(); _file = DatabaseFile.fromBytes(bytes);
_file.deserialize(bytes);
} catch (DatabaseFileException e) { } catch (DatabaseFileException e) {
throw new DatabaseImporterException(e); throw new DatabaseImporterException(e);
} }
@ -35,14 +34,13 @@ public class AegisImporter extends DatabaseImporter {
public List<DatabaseEntry> convert() throws DatabaseImporterException { public List<DatabaseEntry> convert() throws DatabaseImporterException {
try { try {
JSONObject obj; JSONObject obj;
if (_file.isEncrypted() && _key != null) { if (_file.isEncrypted() && _creds != null) {
obj = _file.getContent(_key); obj = _file.getContent(_creds);
} else { } else {
obj = _file.getContent(); obj = _file.getContent();
} }
Database db = new Database(); Database db = Database.fromJson(obj);
db.deserialize(obj);
return db.getEntries(); return db.getEntries();
} catch (DatabaseException | DatabaseFileException e) { } catch (DatabaseException | DatabaseFileException e) {
throw new DatabaseImporterException(e); throw new DatabaseImporterException(e);
@ -54,8 +52,8 @@ public class AegisImporter extends DatabaseImporter {
return _file.isEncrypted(); return _file.isEncrypted();
} }
public void setKey(MasterKey key) { public void setCredentials(DatabaseFileCredentials creds) {
_key = key; _creds = creds;
} }
public DatabaseFile getFile() { public DatabaseFile getFile() {

View file

@ -42,14 +42,6 @@ public abstract class DatabaseImporter {
} }
} }
public static List<DatabaseImporter> create(ByteInputStream stream) {
List<DatabaseImporter> list = new ArrayList<>();
for (Class<? extends DatabaseImporter> type : _importers.values()) {
list.add(create(stream, type));
}
return list;
}
public static Map<String, Class<? extends DatabaseImporter>> getImporters() { public static Map<String, Class<? extends DatabaseImporter>> getImporters() {
return _importers; return _importers;
} }

View file

@ -88,7 +88,7 @@ public abstract class OtpInfo implements Serializable {
_digits = digits; _digits = digits;
} }
public static OtpInfo parseJson(String type, JSONObject obj) throws OtpInfoException { public static OtpInfo fromJson(String type, JSONObject obj) throws OtpInfoException {
OtpInfo info; OtpInfo info;
try { try {

View file

@ -25,6 +25,7 @@ import me.impy.aegis.R;
import me.impy.aegis.crypto.KeyStoreHandle; import me.impy.aegis.crypto.KeyStoreHandle;
import me.impy.aegis.crypto.KeyStoreHandleException; import me.impy.aegis.crypto.KeyStoreHandleException;
import me.impy.aegis.crypto.MasterKey; import me.impy.aegis.crypto.MasterKey;
import me.impy.aegis.db.DatabaseFileCredentials;
import me.impy.aegis.db.slots.FingerprintSlot; import me.impy.aegis.db.slots.FingerprintSlot;
import me.impy.aegis.db.slots.PasswordSlot; import me.impy.aegis.db.slots.PasswordSlot;
import me.impy.aegis.db.slots.Slot; import me.impy.aegis.db.slots.Slot;
@ -128,7 +129,7 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
private void setKey(MasterKey key) { private void setKey(MasterKey key) {
// send the master key back to the main activity // send the master key back to the main activity
Intent result = new Intent(); Intent result = new Intent();
result.putExtra("key", key); result.putExtra("creds", new DatabaseFileCredentials(key, _slots));
setResult(RESULT_OK, result); setResult(RESULT_OK, result);
finish(); finish();
} }

View file

@ -16,7 +16,7 @@ import javax.crypto.SecretKey;
import me.impy.aegis.Preferences; import me.impy.aegis.Preferences;
import me.impy.aegis.R; import me.impy.aegis.R;
import me.impy.aegis.crypto.MasterKey; import me.impy.aegis.db.DatabaseFileCredentials;
import me.impy.aegis.db.DatabaseFileException; import me.impy.aegis.db.DatabaseFileException;
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.FingerprintSlot;
@ -128,9 +128,9 @@ public class IntroActivity extends AppIntro2 implements DerivationTask.Callback
} }
// generate the master key // generate the master key
MasterKey masterKey = null; DatabaseFileCredentials creds = null;
if (cryptType != CustomAuthenticationSlide.CRYPT_TYPE_NONE) { if (cryptType != CustomAuthenticationSlide.CRYPT_TYPE_NONE) {
masterKey = MasterKey.generate(); creds = new DatabaseFileCredentials();
} }
SlotList slots = null; SlotList slots = null;
@ -141,10 +141,8 @@ public class IntroActivity extends AppIntro2 implements DerivationTask.Callback
throw new RuntimeException(); throw new RuntimeException();
} }
try { try {
_passwordSlot.setKey(masterKey, _passwordCipher); _passwordSlot.setKey(creds.getKey(), _passwordCipher);
slots = new SlotList(); creds.getSlots().add(_passwordSlot);
slots.add(_passwordSlot);
_databaseFile.setSlots(slots);
} catch (SlotException e) { } catch (SlotException e) {
setException(e); setException(e);
} }
@ -156,8 +154,8 @@ public class IntroActivity extends AppIntro2 implements DerivationTask.Callback
// and add it to the list of slots // and add it to the list of slots
FingerprintSlot slot = _authenticatedSlide.getFingerSlot(); FingerprintSlot slot = _authenticatedSlide.getFingerSlot();
Cipher cipher = _authenticatedSlide.getFingerCipher(); Cipher cipher = _authenticatedSlide.getFingerCipher();
slot.setKey(masterKey, cipher); slot.setKey(creds.getKey(), cipher);
slots.add(slot); creds.getSlots().add(slot);
} catch (SlotException e) { } catch (SlotException e) {
setException(e); setException(e);
return; return;
@ -166,11 +164,11 @@ public class IntroActivity extends AppIntro2 implements DerivationTask.Callback
// finally, save the database // finally, save the database
try { try {
JSONObject obj = _database.serialize(); JSONObject obj = _database.toJson();
if (cryptType == CustomAuthenticationSlide.CRYPT_TYPE_NONE) { if (cryptType == CustomAuthenticationSlide.CRYPT_TYPE_NONE) {
_databaseFile.setContent(obj); _databaseFile.setContent(obj);
} else { } else {
_databaseFile.setContent(obj, masterKey); _databaseFile.setContent(obj, creds);
} }
DatabaseManager.save(getApplicationContext(), _databaseFile); DatabaseManager.save(getApplicationContext(), _databaseFile);
} catch (DatabaseManagerException | DatabaseFileException e) { } catch (DatabaseManagerException | DatabaseFileException e) {
@ -180,7 +178,7 @@ public class IntroActivity extends AppIntro2 implements DerivationTask.Callback
// send the master key back to the main activity // send the master key back to the main activity
Intent result = new Intent(); Intent result = new Intent();
result.putExtra("key", masterKey); result.putExtra("creds", creds);
setResult(RESULT_OK, result); setResult(RESULT_OK, result);
// skip the intro from now on // skip the intro from now on

View file

@ -20,7 +20,7 @@ import java.lang.reflect.UndeclaredThrowableException;
import me.impy.aegis.AegisApplication; import me.impy.aegis.AegisApplication;
import me.impy.aegis.R; import me.impy.aegis.R;
import me.impy.aegis.crypto.MasterKey; import me.impy.aegis.db.DatabaseFileCredentials;
import me.impy.aegis.db.DatabaseManagerException; import me.impy.aegis.db.DatabaseManagerException;
import me.impy.aegis.db.DatabaseEntry; import me.impy.aegis.db.DatabaseEntry;
import me.impy.aegis.db.DatabaseManager; import me.impy.aegis.db.DatabaseManager;
@ -218,13 +218,13 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
throw new UndeclaredThrowableException(e); throw new UndeclaredThrowableException(e);
} }
MasterKey key = (MasterKey) data.getSerializableExtra("key"); DatabaseFileCredentials creds = (DatabaseFileCredentials) data.getSerializableExtra("creds");
unlockDatabase(key); unlockDatabase(creds);
} }
private void onDecryptResult(int resultCode, Intent intent) { private void onDecryptResult(int resultCode, Intent intent) {
MasterKey key = (MasterKey) intent.getSerializableExtra("key"); DatabaseFileCredentials creds = (DatabaseFileCredentials) intent.getSerializableExtra("creds");
unlockDatabase(key); unlockDatabase(creds);
doShortcutActions(); doShortcutActions();
} }
@ -361,7 +361,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
} }
} }
private void unlockDatabase(MasterKey key) { private void unlockDatabase(DatabaseFileCredentials creds) {
if (_loaded) { if (_loaded) {
return; return;
} }
@ -371,15 +371,14 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
_db.load(); _db.load();
} }
if (_db.isLocked()) { if (_db.isLocked()) {
if (key == null) { if (creds == null) {
startAuthActivity(); startAuthActivity();
return; return;
} else { } else {
_db.unlock(key); _db.unlock(creds);
} }
} }
} catch (DatabaseManagerException e) { } catch (DatabaseManagerException e) {
e.printStackTrace();
Toast.makeText(this, "An error occurred while trying to load/decrypt the database", Toast.LENGTH_LONG).show(); Toast.makeText(this, "An error occurred while trying to load/decrypt the database", Toast.LENGTH_LONG).show();
startAuthActivity(); startAuthActivity();
return; return;
@ -396,7 +395,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
private void startAuthActivity() { private void startAuthActivity() {
Intent intent = new Intent(this, AuthActivity.class); Intent intent = new Intent(this, AuthActivity.class);
intent.putExtra("slots", _db.getFile().getSlots()); intent.putExtra("slots", _db.getFileHeader().getSlots());
startActivityForResult(intent, CODE_DECRYPT); startActivityForResult(intent, CODE_DECRYPT);
} }
@ -404,7 +403,6 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
try { try {
_db.save(); _db.save();
} catch (DatabaseManagerException e) { } catch (DatabaseManagerException e) {
e.printStackTrace();
Toast.makeText(this, "An error occurred while trying to save the database", Toast.LENGTH_LONG).show(); Toast.makeText(this, "An error occurred while trying to save the database", Toast.LENGTH_LONG).show();
} }
} }
@ -413,7 +411,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
// hide the lock icon if the database is not unlocked // hide the lock icon if the database is not unlocked
if (_menu != null && !_db.isLocked()) { if (_menu != null && !_db.isLocked()) {
MenuItem item = _menu.findItem(R.id.action_lock); MenuItem item = _menu.findItem(R.id.action_lock);
item.setVisible(_db.getFile().isEncrypted()); item.setVisible(_db.isEncryptionEnabled());
} }
} }

View file

@ -26,12 +26,12 @@ import javax.crypto.Cipher;
import me.impy.aegis.AegisApplication; import me.impy.aegis.AegisApplication;
import me.impy.aegis.R; import me.impy.aegis.R;
import me.impy.aegis.crypto.MasterKey;
import me.impy.aegis.db.DatabaseEntry; import me.impy.aegis.db.DatabaseEntry;
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.Slot; import me.impy.aegis.db.slots.Slot;
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.PermissionHelper; import me.impy.aegis.helpers.PermissionHelper;
import me.impy.aegis.importers.AegisImporter; import me.impy.aegis.importers.AegisImporter;
@ -138,7 +138,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
_encryptionPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { _encryptionPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override @Override
public boolean onPreferenceChange(Preference preference, Object newValue) { public boolean onPreferenceChange(Preference preference, Object newValue) {
if (!_db.getFile().isEncrypted()) { if (!_db.isEncryptionEnabled()) {
PasswordDialogFragment dialog = new PasswordDialogFragment(); PasswordDialogFragment dialog = new PasswordDialogFragment();
// TODO: find a less ugly way to obtain the fragment manager // TODO: find a less ugly way to obtain the fragment manager
dialog.show(getActivity().getSupportFragmentManager(), null); dialog.show(getActivity().getSupportFragmentManager(), null);
@ -148,8 +148,11 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
.setMessage("Are you sure you want to disable encryption? This will cause the database to be stored in plain text") .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() { .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
_db.disableEncryption(); try {
saveDatabase(); _db.disableEncryption();
} catch (DatabaseManagerException e) {
Toast.makeText(getActivity(), "An error occurred while enabling encryption", Toast.LENGTH_SHORT).show();
}
updateEncryptionPreference(); updateEncryptionPreference();
} }
}) })
@ -163,10 +166,8 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
_slotsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { _slotsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override @Override
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
MasterKey masterKey = _db.getMasterKey();
Intent intent = new Intent(getActivity(), SlotManagerActivity.class); Intent intent = new Intent(getActivity(), SlotManagerActivity.class);
intent.putExtra("masterKey", masterKey); intent.putExtra("creds", _db.getCredentials());
intent.putExtra("slots", _db.getFile().getSlots());
startActivityForResult(intent, CODE_SLOTS); startActivityForResult(intent, CODE_SLOTS);
return true; return true;
} }
@ -249,13 +250,12 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
return; return;
} }
MasterKey key = (MasterKey) data.getSerializableExtra("key"); DatabaseFileCredentials creds = (DatabaseFileCredentials) data.getSerializableExtra("creds");
((AegisImporter)_importer).setKey(key); ((AegisImporter)_importer).setCredentials(creds);
try { try {
importDatabase(_importer); importDatabase(_importer);
} catch (DatabaseImporterException e) { } catch (DatabaseImporterException e) {
e.printStackTrace();
Toast.makeText(getActivity(), "An error occurred while trying to parse the file", Toast.LENGTH_SHORT).show(); Toast.makeText(getActivity(), "An error occurred while trying to parse the file", Toast.LENGTH_SHORT).show();
} }
@ -269,25 +269,16 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
} }
ByteInputStream stream; ByteInputStream stream;
InputStream fileStream = null;
try { try (InputStream fileStream = getActivity().getContentResolver().openInputStream(uri)) {
fileStream = getActivity().getContentResolver().openInputStream(uri);
stream = ByteInputStream.create(fileStream); stream = ByteInputStream.create(fileStream);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
Toast.makeText(getActivity(), "Error: File not found", Toast.LENGTH_SHORT).show(); Toast.makeText(getActivity(), "Error: File not found", Toast.LENGTH_SHORT).show();
return; return;
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace();
Toast.makeText(getActivity(), "An error occurred while trying to read the file", Toast.LENGTH_SHORT).show(); Toast.makeText(getActivity(), "An error occurred while trying to read the file", Toast.LENGTH_SHORT).show();
return; return;
} finally {
if (fileStream != null) {
try {
fileStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
} }
try { try {
@ -299,7 +290,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
_importer = importer; _importer = importer;
Intent intent = new Intent(getActivity(), AuthActivity.class); Intent intent = new Intent(getActivity(), AuthActivity.class);
intent.putExtra("slots", ((AegisImporter)_importer).getFile().getSlots()); intent.putExtra("slots", ((AegisImporter)_importer).getFile().getHeader().getSlots());
startActivityForResult(intent, CODE_IMPORT_DECRYPT); startActivityForResult(intent, CODE_IMPORT_DECRYPT);
return; return;
} }
@ -344,7 +335,6 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
try { try {
filename = _db.export(checked[0]); filename = _db.export(checked[0]);
} catch (DatabaseManagerException e) { } catch (DatabaseManagerException e) {
e.printStackTrace();
Toast.makeText(getActivity(), "An error occurred while trying to export the database", Toast.LENGTH_SHORT).show(); Toast.makeText(getActivity(), "An error occurred while trying to export the database", Toast.LENGTH_SHORT).show();
return; return;
} }
@ -355,7 +345,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
Toast.makeText(getActivity(), "The database has been exported to: " + filename, Toast.LENGTH_SHORT).show(); Toast.makeText(getActivity(), "The database has been exported to: " + filename, Toast.LENGTH_SHORT).show();
}) })
.setNegativeButton(android.R.string.cancel, null); .setNegativeButton(android.R.string.cancel, null);
if (_db.getFile().isEncrypted()) { if (_db.isEncryptionEnabled()) {
final String[] items = {"Keep the database encrypted"}; final String[] items = {"Keep the database encrypted"};
final boolean[] checkedItems = {true}; final boolean[] checkedItems = {true};
builder.setMultiChoiceItems(items, checkedItems, new DialogInterface.OnMultiChoiceClickListener() { builder.setMultiChoiceItems(items, checkedItems, new DialogInterface.OnMultiChoiceClickListener() {
@ -375,8 +365,8 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
return; return;
} }
SlotList slots = (SlotList) data.getSerializableExtra("slots"); DatabaseFileCredentials creds = (DatabaseFileCredentials) data.getSerializableExtra("creds");
_db.getFile().setSlots(slots); _db.setCredentials(creds);
saveDatabase(); saveDatabase();
} }
@ -384,7 +374,6 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
try { try {
_db.save(); _db.save();
} catch (DatabaseManagerException e) { } catch (DatabaseManagerException e) {
e.printStackTrace();
Toast.makeText(getActivity(), "An error occurred while trying to save the database", Toast.LENGTH_LONG).show(); Toast.makeText(getActivity(), "An error occurred while trying to save the database", Toast.LENGTH_LONG).show();
return false; return false;
} }
@ -394,19 +383,17 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
@Override @Override
public void onSlotResult(Slot slot, Cipher cipher) { public void onSlotResult(Slot slot, Cipher cipher) {
MasterKey masterKey = MasterKey.generate(); DatabaseFileCredentials creds = new DatabaseFileCredentials();
SlotList slots = new SlotList();
try { try {
slot.setKey(masterKey, cipher); slot.setKey(creds.getKey(), cipher);
} catch (SlotException e) { creds.getSlots().add(slot);
_db.enableEncryption(creds);
} catch (DatabaseManagerException | SlotException e) {
onException(e); onException(e);
return; return;
} }
slots.add(slot);
_db.enableEncryption(masterKey, slots);
saveDatabase();
updateEncryptionPreference(); updateEncryptionPreference();
} }
@ -417,7 +404,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
} }
private void updateEncryptionPreference() { private void updateEncryptionPreference() {
boolean encrypted = _db.getFile().isEncrypted(); boolean encrypted = _db.isEncryptionEnabled();
_encryptionPreference.setChecked(encrypted, true); _encryptionPreference.setChecked(encrypted, true);
_slotsPreference.setEnabled(encrypted); _slotsPreference.setEnabled(encrypted);
} }

View file

@ -110,10 +110,7 @@ public class ScannerActivity extends AegisActivity implements ZXingScannerView.R
// parse google auth uri // parse google auth uri
Uri uri = Uri.parse(rawResult.getText()); Uri uri = Uri.parse(rawResult.getText());
GoogleAuthInfo info = GoogleAuthInfo.parseUri(uri); GoogleAuthInfo info = GoogleAuthInfo.parseUri(uri);
DatabaseEntry entry = new DatabaseEntry(info);
DatabaseEntry entry = new DatabaseEntry(info.getOtpInfo());
entry.setIssuer(info.getIssuer());
entry.setName(info.getAccountName());
Intent intent = new Intent(); Intent intent = new Intent();
intent.putExtra("entry", entry); intent.putExtra("entry", entry);

View file

@ -16,7 +16,7 @@ import javax.crypto.Cipher;
import me.impy.aegis.R; import me.impy.aegis.R;
import me.impy.aegis.crypto.KeyStoreHandle; import me.impy.aegis.crypto.KeyStoreHandle;
import me.impy.aegis.crypto.KeyStoreHandleException; import me.impy.aegis.crypto.KeyStoreHandleException;
import me.impy.aegis.crypto.MasterKey; import me.impy.aegis.db.DatabaseFileCredentials;
import me.impy.aegis.db.slots.FingerprintSlot; import me.impy.aegis.db.slots.FingerprintSlot;
import me.impy.aegis.db.slots.PasswordSlot; import me.impy.aegis.db.slots.PasswordSlot;
import me.impy.aegis.db.slots.Slot; import me.impy.aegis.db.slots.Slot;
@ -29,8 +29,7 @@ import me.impy.aegis.ui.views.SlotAdapter;
import me.impy.aegis.ui.dialogs.SlotDialogFragment; 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, SlotDialogFragment.Listener {
private MasterKey _masterKey; private DatabaseFileCredentials _creds;
private SlotList _slots;
private SlotAdapter _adapter; private SlotAdapter _adapter;
private boolean _edited; private boolean _edited;
@ -59,9 +58,8 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
slotsView.setNestedScrollingEnabled(false); slotsView.setNestedScrollingEnabled(false);
// load the slots and masterKey // load the slots and masterKey
_masterKey = (MasterKey) getIntent().getSerializableExtra("masterKey"); _creds = (DatabaseFileCredentials) getIntent().getSerializableExtra("creds");
_slots = (SlotList) getIntent().getSerializableExtra("slots"); for (Slot slot : _creds.getSlots()) {
for (Slot slot : _slots) {
_adapter.addSlot(slot); _adapter.addSlot(slot);
} }
@ -75,7 +73,7 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
if (FingerprintHelper.getManager(this) != null) { if (FingerprintHelper.getManager(this) != null) {
try { try {
KeyStoreHandle keyStore = new KeyStoreHandle(); KeyStoreHandle keyStore = new KeyStoreHandle();
for (FingerprintSlot slot : _slots.findAll(FingerprintSlot.class)) { for (FingerprintSlot slot : _creds.getSlots().findAll(FingerprintSlot.class)) {
if (keyStore.containsKey(slot.getUUID().toString())) { if (keyStore.containsKey(slot.getUUID().toString())) {
visibility = View.GONE; visibility = View.GONE;
break; break;
@ -92,7 +90,7 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
private void onSave() { private void onSave() {
Intent intent = new Intent(); Intent intent = new Intent();
intent.putExtra("slots", _slots); intent.putExtra("creds", _creds);
setResult(RESULT_OK, intent); setResult(RESULT_OK, intent);
finish(); finish();
} }
@ -150,7 +148,8 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
@Override @Override
public void onRemoveSlot(Slot slot) { public void onRemoveSlot(Slot slot) {
if (slot instanceof PasswordSlot && _slots.findAll(PasswordSlot.class).size() <= 1) { SlotList slots = _creds.getSlots();
if (slot instanceof PasswordSlot && slots.findAll(PasswordSlot.class).size() <= 1) {
Toast.makeText(this, "You must have at least one password slot", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "You must have at least one password slot", Toast.LENGTH_SHORT).show();
return; return;
} }
@ -159,7 +158,7 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
.setTitle("Remove slot") .setTitle("Remove slot")
.setMessage("Are you sure you want to remove this slot?") .setMessage("Are you sure you want to remove this slot?")
.setPositiveButton(android.R.string.yes, (dialog, whichButton) -> { .setPositiveButton(android.R.string.yes, (dialog, whichButton) -> {
_slots.remove(slot); slots.remove(slot);
_adapter.removeSlot(slot); _adapter.removeSlot(slot);
_edited = true; _edited = true;
updateFingerprintButton(); updateFingerprintButton();
@ -171,13 +170,13 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
@Override @Override
public void onSlotResult(Slot slot, Cipher cipher) { public void onSlotResult(Slot slot, Cipher cipher) {
try { try {
slot.setKey(_masterKey, cipher); slot.setKey(_creds.getKey(), cipher);
} catch (SlotException e) { } catch (SlotException e) {
onException(e); onException(e);
return; return;
} }
_slots.add(slot); _creds.getSlots().add(slot);
_adapter.addSlot(slot); _adapter.addSlot(slot);
_edited = true; _edited = true;
updateFingerprintButton(); updateFingerprintButton();