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;
}
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[] tag = Hex.decode(obj.getString("tag"));
return new CryptParameters(nonce, tag);

View file

@ -12,14 +12,13 @@ import me.impy.aegis.otp.OtpInfoException;
public class Database {
private static final int VERSION = 1;
private DatabaseEntryList _entries = new DatabaseEntryList();
public JSONObject serialize() {
public JSONObject toJson() {
try {
JSONArray array = new JSONArray();
for (DatabaseEntry e : _entries) {
array.put(e.serialize());
array.put(e.toJson());
}
JSONObject obj = new JSONObject();
@ -31,8 +30,9 @@ public class Database {
}
}
public void deserialize(JSONObject obj) throws DatabaseException {
// TODO: support different VERSION deserialization providers
public static Database fromJson(JSONObject obj) throws DatabaseException {
Database db = new Database();
try {
int ver = obj.getInt("version");
if (ver != VERSION) {
@ -41,13 +41,14 @@ public class Database {
JSONArray array = obj.getJSONArray("entries");
for (int i = 0; i < array.length(); i++) {
DatabaseEntry entry = new DatabaseEntry(null);
entry.deserialize(array.getJSONObject(i));
addEntry(entry);
DatabaseEntry entry = DatabaseEntry.fromJson(array.getJSONObject(i));
db.addEntry(entry);
}
} catch (Base64Exception | OtpInfoException | JSONException e) {
throw new DatabaseException(e);
}
return db;
}
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.Base64Exception;
import me.impy.aegis.otp.GoogleAuthInfo;
import me.impy.aegis.otp.OtpInfo;
import me.impy.aegis.otp.OtpInfoException;
@ -19,9 +20,13 @@ public class DatabaseEntry implements Serializable {
private OtpInfo _info;
private byte[] _icon;
public DatabaseEntry(OtpInfo info) {
private DatabaseEntry(UUID uuid, OtpInfo info) {
_uuid = uuid;
_info = info;
_uuid = UUID.randomUUID();
}
public DatabaseEntry(OtpInfo info) {
this(UUID.randomUUID(), info);
}
public DatabaseEntry(OtpInfo info, String name, String issuer) {
@ -30,7 +35,11 @@ public class DatabaseEntry implements Serializable {
setIssuer(issuer);
}
public JSONObject serialize() {
public DatabaseEntry(GoogleAuthInfo info) {
this(info.getOtpInfo(), info.getAccountName(), info.getIssuer());
}
public JSONObject toJson() {
JSONObject obj = new JSONObject();
try {
@ -47,22 +56,26 @@ public class DatabaseEntry implements Serializable {
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
UUID uuid;
if (!obj.has("uuid")) {
_uuid = UUID.randomUUID();
uuid = UUID.randomUUID();
} 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");
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() {

View file

@ -1,6 +1,5 @@
package me.impy.aegis.db;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@ -8,7 +7,6 @@ import java.io.UnsupportedEncodingException;
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;
import me.impy.aegis.db.slots.SlotListException;
@ -20,22 +18,41 @@ public class DatabaseFile {
public static final byte VERSION = 1;
private Object _content;
private CryptParameters _cryptParameters;
private SlotList _slots;
private Header _header;
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 {
// 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();
obj.put("version", VERSION);
obj.put("header", headerObj);
obj.put("header", _header.toJson());
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);
return string.getBytes("UTF-8");
} 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 {
JSONObject obj = new JSONObject(new String(data, "UTF-8"));
JSONObject headerObj = obj.getJSONObject("header");
if (obj.getInt("version") > VERSION) {
throw new DatabaseFileException("unsupported version");
}
JSONArray slotObj = headerObj.optJSONArray("slots");
if (slotObj != null) {
_slots = SlotList.deserialize(slotObj);
Header header = Header.fromJson(obj.getJSONObject("header"));
if (!header.isEmpty()) {
return new DatabaseFile(obj.getString("db"), header);
}
JSONObject cryptObj = headerObj.optJSONObject("params");
if (cryptObj != null) {
_cryptParameters = CryptParameters.parseJson(cryptObj);
}
if (cryptObj == null || slotObj == null) {
_content = obj.getJSONObject("db");
} else {
_content = obj.getString("db");
}
} catch (SlotListException | UnsupportedEncodingException | JSONException | HexException e) {
return new DatabaseFile(obj.getJSONObject("db"), header);
} catch (JSONException e) {
throw new DatabaseFileException(e);
}
}
public boolean isEncrypted() {
return _slots != null;
public static DatabaseFile fromBytes(byte[] data) throws DatabaseFileException {
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() {
return (JSONObject) _content;
}
public JSONObject getContent(MasterKey key) throws DatabaseFileException {
public JSONObject getContent(DatabaseFileCredentials creds) throws DatabaseFileException {
try {
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"));
} catch (MasterKeyException | JSONException | UnsupportedEncodingException | Base64Exception e) {
throw new DatabaseFileException(e);
}
}
public void setContent(JSONObject dbObj) {
_content = dbObj;
_cryptParameters = null;
_slots = null;
public void setContent(JSONObject obj) {
_content = obj;
_header = new Header(null, null);
}
public void setContent(JSONObject dbObj, MasterKey key) throws DatabaseFileException {
public void setContent(JSONObject obj, DatabaseFileCredentials creds) throws DatabaseFileException {
try {
String string = dbObj.toString(4);
String string = obj.toString(4);
byte[] dbBytes = string.getBytes("UTF-8");
CryptResult result = key.encrypt(dbBytes);
CryptResult result = creds.encrypt(dbBytes);
_content = Base64.encode(result.getData());
_cryptParameters = result.getParams();
_header = new Header(creds.getSlots(), result.getParams());
} catch (MasterKeyException | UnsupportedEncodingException | JSONException e) {
throw new DatabaseFileException(e);
}
}
public SlotList getSlots() {
return _slots;
}
public static class Header {
private SlotList _slots;
private CryptParameters _params;
public void setSlots(SlotList slots) {
_slots = slots;
public Header(SlotList slots, CryptParameters params) {
_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 me.impy.aegis.BuildConfig;
import me.impy.aegis.crypto.MasterKey;
import me.impy.aegis.db.slots.SlotList;
public class DatabaseManager {
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";
private MasterKey _key;
private DatabaseFile _file;
private Database _db;
private DatabaseFile _file;
private DatabaseFileCredentials _creds;
private boolean _encrypt;
private Context _context;
public DatabaseManager(Context context) {
@ -39,31 +39,17 @@ public class DatabaseManager {
public void load() throws DatabaseManagerException {
assertState(true, false);
try {
byte[] fileBytes;
FileInputStream file = null;
try (FileInputStream file = _context.openFileInput(FILENAME)) {
byte[] fileBytes = new byte[(int) file.getChannel().size()];
DataInputStream stream = new DataInputStream(file);
stream.readFully(fileBytes);
stream.close();
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();
}
}
_file = new DatabaseFile();
_file.deserialize(fileBytes);
if (!_file.isEncrypted()) {
_file = DatabaseFile.fromBytes(fileBytes);
_encrypt = _file.isEncrypted();
if (!isEncryptionEnabled()) {
JSONObject obj = _file.getContent();
_db = new Database();
_db.deserialize(obj);
_db = Database.fromJson(obj);
}
} catch (IOException | DatabaseFileException | DatabaseException e) {
throw new DatabaseManagerException(e);
@ -73,37 +59,26 @@ public class DatabaseManager {
public void lock() {
assertState(false, true);
// TODO: properly clear everything
_key = null;
_creds = null;
_db = null;
}
public void unlock(MasterKey key) throws DatabaseManagerException {
public void unlock(DatabaseFileCredentials creds) throws DatabaseManagerException {
assertState(true, true);
try {
JSONObject obj = _file.getContent(key);
_db = new Database();
_db.deserialize(obj);
_key = key;
JSONObject obj = _file.getContent(creds);
_db = Database.fromJson(obj);
_creds = creds;
} catch (DatabaseFileException | DatabaseException e) {
throw new DatabaseManagerException(e);
}
}
public static void save(Context context, DatabaseFile file) throws DatabaseManagerException {
try {
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();
}
}
byte[] bytes = file.toBytes();
try (FileOutputStream stream = context.openFileOutput(FILENAME, Context.MODE_PRIVATE)) {
stream.write(bytes);
} catch (IOException e) {
throw new DatabaseManagerException(e);
}
@ -113,9 +88,9 @@ public class DatabaseManager {
assertState(false, true);
try {
JSONObject obj = _db.serialize();
if (_file.isEncrypted()) {
_file.setContent(obj, _key);
JSONObject obj = _db.toJson();
if (isEncryptionEnabled()) {
_file.setContent(obj, _creds);
} else {
_file.setContent(obj);
}
@ -130,31 +105,22 @@ public class DatabaseManager {
try {
DatabaseFile dbFile = new DatabaseFile();
dbFile.setSlots(_file.getSlots());
if (encrypt && getFile().isEncrypted()) {
dbFile.setContent(_db.serialize(), _key);
if (encrypt && isEncryptionEnabled()) {
dbFile.setContent(_db.toJson(), _creds);
} else {
dbFile.setContent(_db.serialize());
dbFile.setContent(_db.toJson());
}
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");
}
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);
byte[] bytes = dbFile.toBytes();
File file = new File(dir.getAbsolutePath(), encrypt ? FILENAME_EXPORT : FILENAME_EXPORT_PLAIN);
try (FileOutputStream stream = new FileOutputStream(file)) {
stream.write(bytes);
} finally {
// always close the file stream
if (stream != null) {
stream.close();
}
}
return file.getAbsolutePath();
@ -193,25 +159,38 @@ public class DatabaseManager {
return _db.getEntryByUUID(uuid);
}
public MasterKey getMasterKey() {
public DatabaseFileCredentials getCredentials() {
assertState(false, true);
return _key;
return _creds;
}
public DatabaseFile getFile() {
return _file;
public void setCredentials(DatabaseFileCredentials creds) {
assertState(false, true);
_creds = creds;
}
public void enableEncryption(MasterKey key, SlotList slots) {
assertState(false, true);
_key = key;
_file.setSlots(slots);
public DatabaseFile.Header getFileHeader() {
assertLoaded(true);
return _file.getHeader();
}
public void disableEncryption() {
public boolean isEncryptionEnabled() {
assertLoaded(true);
return _encrypt;
}
public void enableEncryption(DatabaseFileCredentials creds) throws DatabaseManagerException {
assertState(false, true);
_key = null;
_file.setSlots(null);
_creds = creds;
_encrypt = true;
save();
}
public void disableEncryption() throws DatabaseManagerException {
assertState(false, true);
_creds = null;
_encrypt = false;
save();
}
public boolean isLoaded() {
@ -223,16 +202,20 @@ public class DatabaseManager {
}
private void assertState(boolean locked, boolean loaded) {
if (isLoaded() && !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");
}
assertLoaded(loaded);
if (isLocked() && !locked) {
throw new AssertionError("database file has not been unlocked yet");
} 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
public JSONObject serialize() {
public JSONObject toJson() {
try {
JSONObject obj = super.serialize();
JSONObject obj = super.toJson();
obj.put("n", _n);
obj.put("r", _r);
obj.put("p", _p);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -16,7 +16,7 @@ import javax.crypto.SecretKey;
import me.impy.aegis.Preferences;
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.DatabaseManagerException;
import me.impy.aegis.db.slots.FingerprintSlot;
@ -128,9 +128,9 @@ public class IntroActivity extends AppIntro2 implements DerivationTask.Callback
}
// generate the master key
MasterKey masterKey = null;
DatabaseFileCredentials creds = null;
if (cryptType != CustomAuthenticationSlide.CRYPT_TYPE_NONE) {
masterKey = MasterKey.generate();
creds = new DatabaseFileCredentials();
}
SlotList slots = null;
@ -141,10 +141,8 @@ public class IntroActivity extends AppIntro2 implements DerivationTask.Callback
throw new RuntimeException();
}
try {
_passwordSlot.setKey(masterKey, _passwordCipher);
slots = new SlotList();
slots.add(_passwordSlot);
_databaseFile.setSlots(slots);
_passwordSlot.setKey(creds.getKey(), _passwordCipher);
creds.getSlots().add(_passwordSlot);
} catch (SlotException e) {
setException(e);
}
@ -156,8 +154,8 @@ public class IntroActivity extends AppIntro2 implements DerivationTask.Callback
// and add it to the list of slots
FingerprintSlot slot = _authenticatedSlide.getFingerSlot();
Cipher cipher = _authenticatedSlide.getFingerCipher();
slot.setKey(masterKey, cipher);
slots.add(slot);
slot.setKey(creds.getKey(), cipher);
creds.getSlots().add(slot);
} catch (SlotException e) {
setException(e);
return;
@ -166,11 +164,11 @@ public class IntroActivity extends AppIntro2 implements DerivationTask.Callback
// finally, save the database
try {
JSONObject obj = _database.serialize();
JSONObject obj = _database.toJson();
if (cryptType == CustomAuthenticationSlide.CRYPT_TYPE_NONE) {
_databaseFile.setContent(obj);
} else {
_databaseFile.setContent(obj, masterKey);
_databaseFile.setContent(obj, creds);
}
DatabaseManager.save(getApplicationContext(), _databaseFile);
} 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
Intent result = new Intent();
result.putExtra("key", masterKey);
result.putExtra("creds", creds);
setResult(RESULT_OK, result);
// 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.R;
import me.impy.aegis.crypto.MasterKey;
import me.impy.aegis.db.DatabaseFileCredentials;
import me.impy.aegis.db.DatabaseManagerException;
import me.impy.aegis.db.DatabaseEntry;
import me.impy.aegis.db.DatabaseManager;
@ -218,13 +218,13 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
throw new UndeclaredThrowableException(e);
}
MasterKey key = (MasterKey) data.getSerializableExtra("key");
unlockDatabase(key);
DatabaseFileCredentials creds = (DatabaseFileCredentials) data.getSerializableExtra("creds");
unlockDatabase(creds);
}
private void onDecryptResult(int resultCode, Intent intent) {
MasterKey key = (MasterKey) intent.getSerializableExtra("key");
unlockDatabase(key);
DatabaseFileCredentials creds = (DatabaseFileCredentials) intent.getSerializableExtra("creds");
unlockDatabase(creds);
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) {
return;
}
@ -371,15 +371,14 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
_db.load();
}
if (_db.isLocked()) {
if (key == null) {
if (creds == null) {
startAuthActivity();
return;
} else {
_db.unlock(key);
_db.unlock(creds);
}
}
} catch (DatabaseManagerException e) {
e.printStackTrace();
Toast.makeText(this, "An error occurred while trying to load/decrypt the database", Toast.LENGTH_LONG).show();
startAuthActivity();
return;
@ -396,7 +395,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
private void startAuthActivity() {
Intent intent = new Intent(this, AuthActivity.class);
intent.putExtra("slots", _db.getFile().getSlots());
intent.putExtra("slots", _db.getFileHeader().getSlots());
startActivityForResult(intent, CODE_DECRYPT);
}
@ -404,7 +403,6 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
try {
_db.save();
} catch (DatabaseManagerException e) {
e.printStackTrace();
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
if (_menu != null && !_db.isLocked()) {
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.R;
import me.impy.aegis.crypto.MasterKey;
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.DatabaseManagerException;
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.helpers.PermissionHelper;
import me.impy.aegis.importers.AegisImporter;
@ -138,7 +138,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
_encryptionPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (!_db.getFile().isEncrypted()) {
if (!_db.isEncryptionEnabled()) {
PasswordDialogFragment dialog = new PasswordDialogFragment();
// TODO: find a less ugly way to obtain the fragment manager
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")
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
_db.disableEncryption();
saveDatabase();
try {
_db.disableEncryption();
} catch (DatabaseManagerException e) {
Toast.makeText(getActivity(), "An error occurred while enabling encryption", Toast.LENGTH_SHORT).show();
}
updateEncryptionPreference();
}
})
@ -163,10 +166,8 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
_slotsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
MasterKey masterKey = _db.getMasterKey();
Intent intent = new Intent(getActivity(), SlotManagerActivity.class);
intent.putExtra("masterKey", masterKey);
intent.putExtra("slots", _db.getFile().getSlots());
intent.putExtra("creds", _db.getCredentials());
startActivityForResult(intent, CODE_SLOTS);
return true;
}
@ -249,13 +250,12 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
return;
}
MasterKey key = (MasterKey) data.getSerializableExtra("key");
((AegisImporter)_importer).setKey(key);
DatabaseFileCredentials creds = (DatabaseFileCredentials) data.getSerializableExtra("creds");
((AegisImporter)_importer).setCredentials(creds);
try {
importDatabase(_importer);
} catch (DatabaseImporterException e) {
e.printStackTrace();
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;
InputStream fileStream = null;
try {
fileStream = getActivity().getContentResolver().openInputStream(uri);
try (InputStream fileStream = getActivity().getContentResolver().openInputStream(uri)) {
stream = ByteInputStream.create(fileStream);
} catch (FileNotFoundException e) {
Toast.makeText(getActivity(), "Error: File not found", Toast.LENGTH_SHORT).show();
return;
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(getActivity(), "An error occurred while trying to read the file", Toast.LENGTH_SHORT).show();
return;
} finally {
if (fileStream != null) {
try {
fileStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
try {
@ -299,7 +290,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
_importer = importer;
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);
return;
}
@ -344,7 +335,6 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
try {
filename = _db.export(checked[0]);
} catch (DatabaseManagerException e) {
e.printStackTrace();
Toast.makeText(getActivity(), "An error occurred while trying to export the database", Toast.LENGTH_SHORT).show();
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();
})
.setNegativeButton(android.R.string.cancel, null);
if (_db.getFile().isEncrypted()) {
if (_db.isEncryptionEnabled()) {
final String[] items = {"Keep the database encrypted"};
final boolean[] checkedItems = {true};
builder.setMultiChoiceItems(items, checkedItems, new DialogInterface.OnMultiChoiceClickListener() {
@ -375,8 +365,8 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
return;
}
SlotList slots = (SlotList) data.getSerializableExtra("slots");
_db.getFile().setSlots(slots);
DatabaseFileCredentials creds = (DatabaseFileCredentials) data.getSerializableExtra("creds");
_db.setCredentials(creds);
saveDatabase();
}
@ -384,7 +374,6 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
try {
_db.save();
} catch (DatabaseManagerException e) {
e.printStackTrace();
Toast.makeText(getActivity(), "An error occurred while trying to save the database", Toast.LENGTH_LONG).show();
return false;
}
@ -394,19 +383,17 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
@Override
public void onSlotResult(Slot slot, Cipher cipher) {
MasterKey masterKey = MasterKey.generate();
DatabaseFileCredentials creds = new DatabaseFileCredentials();
SlotList slots = new SlotList();
try {
slot.setKey(masterKey, cipher);
} catch (SlotException e) {
slot.setKey(creds.getKey(), cipher);
creds.getSlots().add(slot);
_db.enableEncryption(creds);
} catch (DatabaseManagerException | SlotException e) {
onException(e);
return;
}
slots.add(slot);
_db.enableEncryption(masterKey, slots);
saveDatabase();
updateEncryptionPreference();
}
@ -417,7 +404,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
}
private void updateEncryptionPreference() {
boolean encrypted = _db.getFile().isEncrypted();
boolean encrypted = _db.isEncryptionEnabled();
_encryptionPreference.setChecked(encrypted, true);
_slotsPreference.setEnabled(encrypted);
}

View file

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

View file

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