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.Collections;
import java.util.List;
import java.util.UUID;
public class Database {
private static final int VERSION = 1;
private List<DatabaseEntry> _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;
}
}

View file

@ -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;
}

View file

@ -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)

View file

@ -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);

View file

@ -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;
}
}

View file

@ -66,6 +66,11 @@ public class SlotCollection implements Iterable<Slot>, 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);
}

View file

@ -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();
}

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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) {

View file

@ -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);
}

View file

@ -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<KeyProfileHolder> 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<KeyProfileHolder> 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<KeyProfileHolder> 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;
}
}

View file

@ -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) { }