Add a UUID to database entries and slots to make merging databases easy

Also, some other fixes for database exporting
This commit is contained in:
Alexander Bakker 2018-03-13 18:30:47 +01:00
parent b27edb1b6b
commit 97c57210f1
14 changed files with 74 additions and 55 deletions

View file

@ -6,12 +6,12 @@ import org.json.JSONObject;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.UUID;
public class Database { public class Database {
private static final int VERSION = 1; private static final int VERSION = 1;
private List<DatabaseEntry> _entries = new ArrayList<>(); private List<DatabaseEntry> _entries = new ArrayList<>();
private long _counter = 0;
public JSONObject serialize() throws Exception { public JSONObject serialize() throws Exception {
JSONArray array = new JSONArray(); JSONArray array = new JSONArray();
@ -26,10 +26,6 @@ public class Database {
} }
public void deserialize(JSONObject obj) throws Exception { 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 // TODO: support different VERSION deserialization providers
int ver = obj.getInt("version"); int ver = obj.getInt("version");
if (ver != VERSION) { if (ver != VERSION) {
@ -40,30 +36,24 @@ public class Database {
for (int i = 0; i < array.length(); i++) { for (int i = 0; i < array.length(); i++) {
DatabaseEntry entry = new DatabaseEntry(null); DatabaseEntry entry = new DatabaseEntry(null);
entry.deserialize(array.getJSONObject(i)); 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); addKey(entry);
} else {
_entries.add(entry);
}
} }
} }
public void addKey(DatabaseEntry entry) throws Exception { public void addKey(DatabaseEntry entry) {
entry.setID(++_counter); if (tryGetKeyByUUID(entry.getUUID()) != null) {
throw new AssertionError("entry found with the same uuid");
}
_entries.add(entry); _entries.add(entry);
} }
public void removeKey(DatabaseEntry entry) { public void removeKey(DatabaseEntry entry) {
entry = getKeyByID(entry.getID()); entry = getKeyByUUID(entry.getUUID());
_entries.remove(entry); _entries.remove(entry);
} }
public void replaceKey(DatabaseEntry newEntry) { public void replaceKey(DatabaseEntry newEntry) {
DatabaseEntry oldEntry = getKeyByID(newEntry.getID()); DatabaseEntry oldEntry = getKeyByUUID(newEntry.getUUID());
_entries.set(_entries.indexOf(oldEntry), newEntry); _entries.set(_entries.indexOf(oldEntry), newEntry);
} }
@ -75,12 +65,20 @@ public class Database {
return Collections.unmodifiableList(_entries); return Collections.unmodifiableList(_entries);
} }
private DatabaseEntry getKeyByID(long id) { private DatabaseEntry tryGetKeyByUUID(UUID uuid) {
for (DatabaseEntry entry : _entries) { for (DatabaseEntry entry : _entries) {
if (entry.getID() == id) { if (entry.getUUID().equals(uuid)) {
return entry; 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;
} }
} }

View file

@ -4,11 +4,13 @@ import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.io.Serializable; import java.io.Serializable;
import java.util.UUID;
import me.impy.aegis.crypto.KeyInfo; import me.impy.aegis.crypto.KeyInfo;
import me.impy.aegis.crypto.KeyInfoException;
public class DatabaseEntry implements Serializable { public class DatabaseEntry implements Serializable {
private long _id = -1; private UUID _uuid;
private String _name = ""; private String _name = "";
private String _icon = ""; private String _icon = "";
private KeyInfo _info; private KeyInfo _info;
@ -19,22 +21,30 @@ public class DatabaseEntry implements Serializable {
public DatabaseEntry(KeyInfo info) { public DatabaseEntry(KeyInfo info) {
_info = info; _info = info;
_uuid = UUID.randomUUID();
} }
public JSONObject serialize() throws JSONException { public JSONObject serialize() throws JSONException {
JSONObject obj = new JSONObject(); JSONObject obj = new JSONObject();
obj.put("uuid", _uuid.toString());
obj.put("name", _name); obj.put("name", _name);
obj.put("url", _info.getURL()); obj.put("url", _info.getURL());
return obj; 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"); _name = obj.getString("name");
_info = KeyInfo.fromURL(obj.getString("url")); _info = KeyInfo.fromURL(obj.getString("url"));
} }
public long getID() { public UUID getUUID() {
return _id; return _uuid;
} }
public String getName() { public String getName() {
return _name; return _name;
@ -46,12 +56,6 @@ public class DatabaseEntry implements Serializable {
return _info; 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) { public void setName(String name) {
_name = name; _name = name;
} }

View file

@ -40,9 +40,11 @@ public class DatabaseFile {
cryptObj.put("tag", Hex.toString(_cryptParameters.Tag)); 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(); JSONObject headerObj = new JSONObject();
headerObj.put("slots", _slots.isEmpty() ? JSONObject.NULL : SlotCollection.serialize(_slots)); headerObj.put("slots", plain ? JSONObject.NULL : SlotCollection.serialize(_slots));
headerObj.put("params", cryptObj != null ? cryptObj : JSONObject.NULL); headerObj.put("params", plain ? JSONObject.NULL : cryptObj);
JSONObject obj = new JSONObject(); JSONObject obj = new JSONObject();
obj.put("version", VERSION); obj.put("version", VERSION);
@ -99,6 +101,7 @@ public class DatabaseFile {
public void setContent(JSONObject dbObj) { public void setContent(JSONObject dbObj) {
_content = dbObj; _content = dbObj;
_cryptParameters = null;
} }
public void setContent(JSONObject dbObj, MasterKey key) public void setContent(JSONObject dbObj, MasterKey key)

View file

@ -108,10 +108,13 @@ public class DatabaseManager {
public String export(boolean encrypt) throws Exception { public String export(boolean encrypt) throws Exception {
assertState(false, true); assertState(false, true);
DatabaseFile dbFile = new DatabaseFile();
dbFile.setSlots(_file.getSlots());
if (encrypt && getFile().isEncrypted()) { if (encrypt && getFile().isEncrypted()) {
_file.setContent(_db.serialize(), _key); dbFile.setContent(_db.serialize(), _key);
} else { } else {
_file.setContent(_db.serialize()); dbFile.setContent(_db.serialize());
} }
File file; File file;
@ -123,7 +126,7 @@ public class DatabaseManager {
throw new IOException("error creating external storage directory"); 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); file = new File(dir.getAbsolutePath(), encrypt ? FILENAME_EXPORT : FILENAME_EXPORT_PLAIN);
stream = new FileOutputStream(file); stream = new FileOutputStream(file);
stream.write(bytes); stream.write(bytes);

View file

@ -8,6 +8,7 @@ import org.json.JSONObject;
import java.io.Serializable; import java.io.Serializable;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.UUID;
import javax.crypto.BadPaddingException; import javax.crypto.BadPaddingException;
import javax.crypto.Cipher; 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_RAW = 0x00;
public final static byte TYPE_DERIVED = 0x01; public final static byte TYPE_DERIVED = 0x01;
public final static byte TYPE_FINGERPRINT = 0x02; public final static byte TYPE_FINGERPRINT = 0x02;
public final static int ID_SIZE = 16;
protected byte[] _id; protected UUID _uuid;
protected byte[] _encryptedMasterKey; protected byte[] _encryptedMasterKey;
protected Slot() { 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. // 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 { public JSONObject serialize() throws JSONException {
JSONObject obj = new JSONObject(); JSONObject obj = new JSONObject();
obj.put("type", getType()); obj.put("type", getType());
obj.put("id", Hex.toString(_id)); obj.put("uuid", _uuid.toString());
obj.put("key", Hex.toString(_encryptedMasterKey)); obj.put("key", Hex.toString(_encryptedMasterKey));
return obj; return obj;
} }
@ -66,12 +66,18 @@ public abstract class Slot implements Serializable {
if (obj.getInt("type") != getType()) { if (obj.getInt("type") != getType()) {
throw new Exception("slot type mismatch"); 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")); _encryptedMasterKey = Hex.toBytes(obj.getString("key"));
} }
public abstract byte getType(); public abstract byte getType();
public String getID() {
return Hex.toString(_id); public UUID getUUID() {
return _uuid;
} }
} }

View file

@ -66,6 +66,11 @@ public class SlotCollection implements Iterable<Slot>, Serializable {
} }
public void add(Slot slot) { 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); _slots.add(slot);
} }

View file

@ -19,7 +19,7 @@ public class AegisImporter extends DatabaseImporter {
DatabaseFile file = new DatabaseFile(); DatabaseFile file = new DatabaseFile();
file.deserialize(bytes); file.deserialize(bytes);
Database db = new Database(); Database db = new Database();
db.deserialize(file.getContent(), false); db.deserialize(file.getContent());
return db.getKeys(); return db.getKeys();
} }

View file

@ -79,8 +79,7 @@ public class FreeOTPImporter extends DatabaseImporter {
byte[] secret = toBytes(obj.getJSONArray("secret")); byte[] secret = toBytes(obj.getJSONArray("secret"));
key.setSecret(secret); key.setSecret(secret);
DatabaseEntry profile = new DatabaseEntry(null); DatabaseEntry profile = new DatabaseEntry(key);
profile.setInfo(key);
profiles.add(profile); profiles.add(profile);
} }
} }

View file

@ -64,7 +64,7 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
try { try {
// find a fingerprint slot with an id that matches an alias in the keystore // find a fingerprint slot with an id that matches an alias in the keystore
for (FingerprintSlot slot : _slots.findAll(FingerprintSlot.class)) { for (FingerprintSlot slot : _slots.findAll(FingerprintSlot.class)) {
String id = slot.getID(); String id = slot.getUUID().toString();
KeyStoreHandle handle = new KeyStoreHandle(); KeyStoreHandle handle = new KeyStoreHandle();
if (handle.containsKey(id)) { if (handle.containsKey(id)) {
SecretKey key = handle.getKey(id); SecretKey key = handle.getKey(id);

View file

@ -79,7 +79,7 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
try { try {
KeyStoreHandle keyStore = new KeyStoreHandle(); KeyStoreHandle keyStore = new KeyStoreHandle();
for (FingerprintSlot slot : _slots.findAll(FingerprintSlot.class)) { 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; visibility = View.GONE;
break; break;
} }

View file

@ -35,7 +35,7 @@ public class FingerprintDialogFragment extends SlotDialogFragment implements Fin
FingerprintManager manager = FingerprintHelper.getManager(getContext()); FingerprintManager manager = FingerprintHelper.getManager(getContext());
try { try {
_slot = new FingerprintSlot(); _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); _cipher = Slot.createCipher(key, Cipher.ENCRYPT_MODE);
_helper = new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, this); _helper = new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, this);
} catch (Exception e) { } catch (Exception e) {

View file

@ -101,7 +101,7 @@ public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiH
_storeHandle = new KeyStoreHandle(); _storeHandle = new KeyStoreHandle();
_fingerSlot = new FingerprintSlot(); _fingerSlot = new FingerprintSlot();
} }
key = _storeHandle.generateKey(_fingerSlot.getID()); key = _storeHandle.generateKey(_fingerSlot.getUUID().toString());
} catch (Exception e) { } catch (Exception e) {
throw new UndeclaredThrowableException(e); throw new UndeclaredThrowableException(e);
} }

View file

@ -7,6 +7,7 @@ import android.view.ViewGroup;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.UUID;
import me.impy.aegis.R; import me.impy.aegis.R;
import me.impy.aegis.helpers.ItemTouchHelperAdapter; import me.impy.aegis.helpers.ItemTouchHelperAdapter;
@ -37,7 +38,7 @@ public class KeyProfileAdapter extends RecyclerView.Adapter<KeyProfileHolder> im
} }
public void removeKey(KeyProfile profile) { public void removeKey(KeyProfile profile) {
profile = getKeyByID(profile.getEntry().getID()); profile = getKeyByUUID(profile.getEntry().getUUID());
int position = _keyProfiles.indexOf(profile); int position = _keyProfiles.indexOf(profile);
_keyProfiles.remove(position); _keyProfiles.remove(position);
notifyItemRemoved(position); notifyItemRemoved(position);
@ -49,7 +50,7 @@ public class KeyProfileAdapter extends RecyclerView.Adapter<KeyProfileHolder> im
} }
public void replaceKey(KeyProfile newProfile) { public void replaceKey(KeyProfile newProfile) {
KeyProfile oldProfile = getKeyByID(newProfile.getEntry().getID()); KeyProfile oldProfile = getKeyByUUID(newProfile.getEntry().getUUID());
int position = _keyProfiles.indexOf(oldProfile); int position = _keyProfiles.indexOf(oldProfile);
_keyProfiles.set(position, newProfile); _keyProfiles.set(position, newProfile);
notifyItemChanged(position); notifyItemChanged(position);
@ -62,9 +63,9 @@ public class KeyProfileAdapter extends RecyclerView.Adapter<KeyProfileHolder> im
notifyDataSetChanged(); notifyDataSetChanged();
} }
private KeyProfile getKeyByID(long id) { private KeyProfile getKeyByUUID(UUID uuid) {
for (KeyProfile profile : _keyProfiles) { for (KeyProfile profile : _keyProfiles) {
if (profile.getEntry().getID() == id) { if (profile.getEntry().getUUID().equals(uuid)) {
return profile; return profile;
} }
} }

View file

@ -41,7 +41,7 @@ public class SlotHolder extends RecyclerView.ViewHolder {
if (FingerprintHelper.isSupported()) { if (FingerprintHelper.isSupported()) {
try { try {
KeyStoreHandle keyStore = new KeyStoreHandle(); KeyStoreHandle keyStore = new KeyStoreHandle();
if (keyStore.containsKey(slot.getID())) { if (keyStore.containsKey(slot.getUUID().toString())) {
_slotUsed.setVisibility(View.VISIBLE); _slotUsed.setVisibility(View.VISIBLE);
} }
} catch (KeyStoreHandleException e) { } } catch (KeyStoreHandleException e) { }