diff --git a/app/src/main/java/me/impy/aegis/db/Database.java b/app/src/main/java/me/impy/aegis/db/Database.java index a4075610..f68439fc 100644 --- a/app/src/main/java/me/impy/aegis/db/Database.java +++ b/app/src/main/java/me/impy/aegis/db/Database.java @@ -6,12 +6,12 @@ import org.json.JSONObject; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.UUID; public class Database { private static final int VERSION = 1; private List _entries = new ArrayList<>(); - private long _counter = 0; public JSONObject serialize() throws Exception { JSONArray array = new JSONArray(); @@ -26,10 +26,6 @@ public class Database { } public void deserialize(JSONObject obj) throws Exception { - deserialize(obj, true); - } - - public void deserialize(JSONObject obj, boolean incCount) throws Exception { // TODO: support different VERSION deserialization providers int ver = obj.getInt("version"); if (ver != VERSION) { @@ -40,30 +36,24 @@ public class Database { for (int i = 0; i < array.length(); i++) { DatabaseEntry entry = new DatabaseEntry(null); entry.deserialize(array.getJSONObject(i)); - - // if incCount is false, don't increment the counter and don't set an ID - // this is used by the database importer to prevent an exception down the line - // TODO: find a better solution for this ugly hack - if (incCount) { - addKey(entry); - } else { - _entries.add(entry); - } + addKey(entry); } } - public void addKey(DatabaseEntry entry) throws Exception { - entry.setID(++_counter); + public void addKey(DatabaseEntry entry) { + if (tryGetKeyByUUID(entry.getUUID()) != null) { + throw new AssertionError("entry found with the same uuid"); + } _entries.add(entry); } public void removeKey(DatabaseEntry entry) { - entry = getKeyByID(entry.getID()); + entry = getKeyByUUID(entry.getUUID()); _entries.remove(entry); } public void replaceKey(DatabaseEntry newEntry) { - DatabaseEntry oldEntry = getKeyByID(newEntry.getID()); + DatabaseEntry oldEntry = getKeyByUUID(newEntry.getUUID()); _entries.set(_entries.indexOf(oldEntry), newEntry); } @@ -75,12 +65,20 @@ public class Database { return Collections.unmodifiableList(_entries); } - private DatabaseEntry getKeyByID(long id) { + private DatabaseEntry tryGetKeyByUUID(UUID uuid) { for (DatabaseEntry entry : _entries) { - if (entry.getID() == id) { + if (entry.getUUID().equals(uuid)) { return entry; } } - throw new AssertionError("no entry found with the same id"); + return null; + } + + private DatabaseEntry getKeyByUUID(UUID uuid) { + DatabaseEntry entry = tryGetKeyByUUID(uuid); + if (entry == null) { + throw new AssertionError("no entry found with the same uuid"); + } + return entry; } } diff --git a/app/src/main/java/me/impy/aegis/db/DatabaseEntry.java b/app/src/main/java/me/impy/aegis/db/DatabaseEntry.java index c3a4008d..564ed88f 100644 --- a/app/src/main/java/me/impy/aegis/db/DatabaseEntry.java +++ b/app/src/main/java/me/impy/aegis/db/DatabaseEntry.java @@ -4,11 +4,13 @@ import org.json.JSONException; import org.json.JSONObject; import java.io.Serializable; +import java.util.UUID; import me.impy.aegis.crypto.KeyInfo; +import me.impy.aegis.crypto.KeyInfoException; public class DatabaseEntry implements Serializable { - private long _id = -1; + private UUID _uuid; private String _name = ""; private String _icon = ""; private KeyInfo _info; @@ -19,22 +21,30 @@ public class DatabaseEntry implements Serializable { public DatabaseEntry(KeyInfo info) { _info = info; + _uuid = UUID.randomUUID(); } public JSONObject serialize() throws JSONException { JSONObject obj = new JSONObject(); + obj.put("uuid", _uuid.toString()); obj.put("name", _name); obj.put("url", _info.getURL()); return obj; } - public void deserialize(JSONObject obj) throws Exception { + public void deserialize(JSONObject obj) throws JSONException, KeyInfoException { + // if there is no uuid, generate a new one + if (!obj.has("uuid")) { + _uuid = UUID.randomUUID(); + } else { + _uuid = UUID.fromString(obj.getString("uuid")); + } _name = obj.getString("name"); _info = KeyInfo.fromURL(obj.getString("url")); } - public long getID() { - return _id; + public UUID getUUID() { + return _uuid; } public String getName() { return _name; @@ -46,12 +56,6 @@ public class DatabaseEntry implements Serializable { return _info; } - void setID(long id) throws Exception { - if (_id != -1) { - throw new Exception("this entry has already received an id"); - } - _id = id; - } public void setName(String name) { _name = name; } diff --git a/app/src/main/java/me/impy/aegis/db/DatabaseFile.java b/app/src/main/java/me/impy/aegis/db/DatabaseFile.java index 51e8736b..24ce5a97 100644 --- a/app/src/main/java/me/impy/aegis/db/DatabaseFile.java +++ b/app/src/main/java/me/impy/aegis/db/DatabaseFile.java @@ -40,9 +40,11 @@ public class DatabaseFile { cryptObj.put("tag", Hex.toString(_cryptParameters.Tag)); } + // don't write the crypt parameters if the content is not encrypted + boolean plain = _content instanceof JSONObject || _slots.isEmpty() || cryptObj == null; JSONObject headerObj = new JSONObject(); - headerObj.put("slots", _slots.isEmpty() ? JSONObject.NULL : SlotCollection.serialize(_slots)); - headerObj.put("params", cryptObj != null ? cryptObj : JSONObject.NULL); + headerObj.put("slots", plain ? JSONObject.NULL : SlotCollection.serialize(_slots)); + headerObj.put("params", plain ? JSONObject.NULL : cryptObj); JSONObject obj = new JSONObject(); obj.put("version", VERSION); @@ -99,6 +101,7 @@ public class DatabaseFile { public void setContent(JSONObject dbObj) { _content = dbObj; + _cryptParameters = null; } public void setContent(JSONObject dbObj, MasterKey key) diff --git a/app/src/main/java/me/impy/aegis/db/DatabaseManager.java b/app/src/main/java/me/impy/aegis/db/DatabaseManager.java index 1eeb4920..1cadb9d8 100644 --- a/app/src/main/java/me/impy/aegis/db/DatabaseManager.java +++ b/app/src/main/java/me/impy/aegis/db/DatabaseManager.java @@ -108,10 +108,13 @@ public class DatabaseManager { public String export(boolean encrypt) throws Exception { assertState(false, true); + + DatabaseFile dbFile = new DatabaseFile(); + dbFile.setSlots(_file.getSlots()); if (encrypt && getFile().isEncrypted()) { - _file.setContent(_db.serialize(), _key); + dbFile.setContent(_db.serialize(), _key); } else { - _file.setContent(_db.serialize()); + dbFile.setContent(_db.serialize()); } File file; @@ -123,7 +126,7 @@ public class DatabaseManager { throw new IOException("error creating external storage directory"); } - byte[] bytes = _file.serialize(); + byte[] bytes = dbFile.serialize(); file = new File(dir.getAbsolutePath(), encrypt ? FILENAME_EXPORT : FILENAME_EXPORT_PLAIN); stream = new FileOutputStream(file); stream.write(bytes); diff --git a/app/src/main/java/me/impy/aegis/db/slots/Slot.java b/app/src/main/java/me/impy/aegis/db/slots/Slot.java index a5d8ec9a..ab054dba 100644 --- a/app/src/main/java/me/impy/aegis/db/slots/Slot.java +++ b/app/src/main/java/me/impy/aegis/db/slots/Slot.java @@ -8,6 +8,7 @@ import org.json.JSONObject; import java.io.Serializable; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; +import java.util.UUID; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; @@ -24,13 +25,12 @@ public abstract class Slot implements Serializable { public final static byte TYPE_RAW = 0x00; public final static byte TYPE_DERIVED = 0x01; public final static byte TYPE_FINGERPRINT = 0x02; - public final static int ID_SIZE = 16; - protected byte[] _id; + protected UUID _uuid; protected byte[] _encryptedMasterKey; protected Slot() { - _id = CryptoUtils.generateRandomBytes(ID_SIZE); + _uuid = UUID.randomUUID(); } // getKey decrypts the encrypted master key in this slot with the given key and returns it. @@ -57,7 +57,7 @@ public abstract class Slot implements Serializable { public JSONObject serialize() throws JSONException { JSONObject obj = new JSONObject(); obj.put("type", getType()); - obj.put("id", Hex.toString(_id)); + obj.put("uuid", _uuid.toString()); obj.put("key", Hex.toString(_encryptedMasterKey)); return obj; } @@ -66,12 +66,18 @@ public abstract class Slot implements Serializable { if (obj.getInt("type") != getType()) { throw new Exception("slot type mismatch"); } - _id = Hex.toBytes(obj.getString("id")); + // if there is no uuid, generate a new one + if (!obj.has("uuid")) { + _uuid = UUID.randomUUID(); + } else { + _uuid = UUID.fromString(obj.getString("uuid")); + } _encryptedMasterKey = Hex.toBytes(obj.getString("key")); } public abstract byte getType(); - public String getID() { - return Hex.toString(_id); + + public UUID getUUID() { + return _uuid; } } diff --git a/app/src/main/java/me/impy/aegis/db/slots/SlotCollection.java b/app/src/main/java/me/impy/aegis/db/slots/SlotCollection.java index e34b2db5..b6a16030 100644 --- a/app/src/main/java/me/impy/aegis/db/slots/SlotCollection.java +++ b/app/src/main/java/me/impy/aegis/db/slots/SlotCollection.java @@ -66,6 +66,11 @@ public class SlotCollection implements Iterable, Serializable { } public void add(Slot slot) { + for (Slot s : this) { + if (s.getUUID().equals(slot.getUUID())) { + throw new AssertionError("slot found with the same uuid"); + } + } _slots.add(slot); } diff --git a/app/src/main/java/me/impy/aegis/importers/AegisImporter.java b/app/src/main/java/me/impy/aegis/importers/AegisImporter.java index 9f42ce67..4f24619c 100644 --- a/app/src/main/java/me/impy/aegis/importers/AegisImporter.java +++ b/app/src/main/java/me/impy/aegis/importers/AegisImporter.java @@ -19,7 +19,7 @@ public class AegisImporter extends DatabaseImporter { DatabaseFile file = new DatabaseFile(); file.deserialize(bytes); Database db = new Database(); - db.deserialize(file.getContent(), false); + db.deserialize(file.getContent()); return db.getKeys(); } diff --git a/app/src/main/java/me/impy/aegis/importers/FreeOTPImporter.java b/app/src/main/java/me/impy/aegis/importers/FreeOTPImporter.java index ec39fa2c..17d0925d 100644 --- a/app/src/main/java/me/impy/aegis/importers/FreeOTPImporter.java +++ b/app/src/main/java/me/impy/aegis/importers/FreeOTPImporter.java @@ -79,8 +79,7 @@ public class FreeOTPImporter extends DatabaseImporter { byte[] secret = toBytes(obj.getJSONArray("secret")); key.setSecret(secret); - DatabaseEntry profile = new DatabaseEntry(null); - profile.setInfo(key); + DatabaseEntry profile = new DatabaseEntry(key); profiles.add(profile); } } diff --git a/app/src/main/java/me/impy/aegis/ui/AuthActivity.java b/app/src/main/java/me/impy/aegis/ui/AuthActivity.java index 9bf4b21c..67f9b527 100644 --- a/app/src/main/java/me/impy/aegis/ui/AuthActivity.java +++ b/app/src/main/java/me/impy/aegis/ui/AuthActivity.java @@ -64,7 +64,7 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C try { // find a fingerprint slot with an id that matches an alias in the keystore for (FingerprintSlot slot : _slots.findAll(FingerprintSlot.class)) { - String id = slot.getID(); + String id = slot.getUUID().toString(); KeyStoreHandle handle = new KeyStoreHandle(); if (handle.containsKey(id)) { SecretKey key = handle.getKey(id); diff --git a/app/src/main/java/me/impy/aegis/ui/SlotManagerActivity.java b/app/src/main/java/me/impy/aegis/ui/SlotManagerActivity.java index 888183fb..fde3a7d2 100644 --- a/app/src/main/java/me/impy/aegis/ui/SlotManagerActivity.java +++ b/app/src/main/java/me/impy/aegis/ui/SlotManagerActivity.java @@ -79,7 +79,7 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li try { KeyStoreHandle keyStore = new KeyStoreHandle(); for (FingerprintSlot slot : _slots.findAll(FingerprintSlot.class)) { - if (keyStore.containsKey(slot.getID()) && FingerprintHelper.getManager(this) != null) { + if (keyStore.containsKey(slot.getUUID().toString()) && FingerprintHelper.getManager(this) != null) { visibility = View.GONE; break; } diff --git a/app/src/main/java/me/impy/aegis/ui/dialogs/FingerprintDialogFragment.java b/app/src/main/java/me/impy/aegis/ui/dialogs/FingerprintDialogFragment.java index d1ac0be4..1321dbb8 100644 --- a/app/src/main/java/me/impy/aegis/ui/dialogs/FingerprintDialogFragment.java +++ b/app/src/main/java/me/impy/aegis/ui/dialogs/FingerprintDialogFragment.java @@ -35,7 +35,7 @@ public class FingerprintDialogFragment extends SlotDialogFragment implements Fin FingerprintManager manager = FingerprintHelper.getManager(getContext()); try { _slot = new FingerprintSlot(); - SecretKey key = new KeyStoreHandle().generateKey(_slot.getID()); + SecretKey key = new KeyStoreHandle().generateKey(_slot.getUUID().toString()); _cipher = Slot.createCipher(key, Cipher.ENCRYPT_MODE); _helper = new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, this); } catch (Exception e) { diff --git a/app/src/main/java/me/impy/aegis/ui/slides/CustomAuthenticatedSlide.java b/app/src/main/java/me/impy/aegis/ui/slides/CustomAuthenticatedSlide.java index 6146f6b0..d3ab78a4 100644 --- a/app/src/main/java/me/impy/aegis/ui/slides/CustomAuthenticatedSlide.java +++ b/app/src/main/java/me/impy/aegis/ui/slides/CustomAuthenticatedSlide.java @@ -101,7 +101,7 @@ public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiH _storeHandle = new KeyStoreHandle(); _fingerSlot = new FingerprintSlot(); } - key = _storeHandle.generateKey(_fingerSlot.getID()); + key = _storeHandle.generateKey(_fingerSlot.getUUID().toString()); } catch (Exception e) { throw new UndeclaredThrowableException(e); } diff --git a/app/src/main/java/me/impy/aegis/ui/views/KeyProfileAdapter.java b/app/src/main/java/me/impy/aegis/ui/views/KeyProfileAdapter.java index b82318ae..ec316583 100644 --- a/app/src/main/java/me/impy/aegis/ui/views/KeyProfileAdapter.java +++ b/app/src/main/java/me/impy/aegis/ui/views/KeyProfileAdapter.java @@ -7,6 +7,7 @@ import android.view.ViewGroup; import java.util.ArrayList; import java.util.Collections; +import java.util.UUID; import me.impy.aegis.R; import me.impy.aegis.helpers.ItemTouchHelperAdapter; @@ -37,7 +38,7 @@ public class KeyProfileAdapter extends RecyclerView.Adapter im } public void removeKey(KeyProfile profile) { - profile = getKeyByID(profile.getEntry().getID()); + profile = getKeyByUUID(profile.getEntry().getUUID()); int position = _keyProfiles.indexOf(profile); _keyProfiles.remove(position); notifyItemRemoved(position); @@ -49,7 +50,7 @@ public class KeyProfileAdapter extends RecyclerView.Adapter im } public void replaceKey(KeyProfile newProfile) { - KeyProfile oldProfile = getKeyByID(newProfile.getEntry().getID()); + KeyProfile oldProfile = getKeyByUUID(newProfile.getEntry().getUUID()); int position = _keyProfiles.indexOf(oldProfile); _keyProfiles.set(position, newProfile); notifyItemChanged(position); @@ -62,9 +63,9 @@ public class KeyProfileAdapter extends RecyclerView.Adapter im notifyDataSetChanged(); } - private KeyProfile getKeyByID(long id) { + private KeyProfile getKeyByUUID(UUID uuid) { for (KeyProfile profile : _keyProfiles) { - if (profile.getEntry().getID() == id) { + if (profile.getEntry().getUUID().equals(uuid)) { return profile; } } diff --git a/app/src/main/java/me/impy/aegis/ui/views/SlotHolder.java b/app/src/main/java/me/impy/aegis/ui/views/SlotHolder.java index 8d6002cb..dd661231 100644 --- a/app/src/main/java/me/impy/aegis/ui/views/SlotHolder.java +++ b/app/src/main/java/me/impy/aegis/ui/views/SlotHolder.java @@ -41,7 +41,7 @@ public class SlotHolder extends RecyclerView.ViewHolder { if (FingerprintHelper.isSupported()) { try { KeyStoreHandle keyStore = new KeyStoreHandle(); - if (keyStore.containsKey(slot.getID())) { + if (keyStore.containsKey(slot.getUUID().toString())) { _slotUsed.setVisibility(View.VISIBLE); } } catch (KeyStoreHandleException e) { }