mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-05-14 05:52:52 +00:00
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:
parent
b27edb1b6b
commit
97c57210f1
14 changed files with 74 additions and 55 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) { }
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue