mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-05-19 16:30:23 +00:00
Rename package to com.beemdevelopment.aegis
This commit is contained in:
parent
ceb03de240
commit
62425511a1
101 changed files with 366 additions and 362 deletions
77
app/src/main/java/com/beemdevelopment/aegis/db/Database.java
Normal file
77
app/src/main/java/com/beemdevelopment/aegis/db/Database.java
Normal file
|
@ -0,0 +1,77 @@
|
|||
package com.beemdevelopment.aegis.db;
|
||||
|
||||
import com.beemdevelopment.aegis.encoding.Base64Exception;
|
||||
import com.beemdevelopment.aegis.otp.OtpInfoException;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class Database {
|
||||
private static final int VERSION = 1;
|
||||
private DatabaseEntryList _entries = new DatabaseEntryList();
|
||||
|
||||
public JSONObject toJson() {
|
||||
try {
|
||||
JSONArray array = new JSONArray();
|
||||
for (DatabaseEntry e : _entries) {
|
||||
array.put(e.toJson());
|
||||
}
|
||||
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("version", VERSION);
|
||||
obj.put("entries", array);
|
||||
return obj;
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Database fromJson(JSONObject obj) throws DatabaseException {
|
||||
Database db = new Database();
|
||||
|
||||
try {
|
||||
int ver = obj.getInt("version");
|
||||
if (ver != VERSION) {
|
||||
throw new DatabaseException("Unsupported version");
|
||||
}
|
||||
|
||||
JSONArray array = obj.getJSONArray("entries");
|
||||
for (int i = 0; i < array.length(); i++) {
|
||||
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) {
|
||||
_entries.add(entry);
|
||||
}
|
||||
|
||||
public void removeEntry(DatabaseEntry entry) {
|
||||
_entries.remove(entry);
|
||||
}
|
||||
|
||||
public void replaceEntry(DatabaseEntry newEntry) {
|
||||
_entries.replace(newEntry);
|
||||
}
|
||||
|
||||
public void swapEntries(DatabaseEntry entry1, DatabaseEntry entry2) {
|
||||
_entries.swap(entry1, entry2);
|
||||
}
|
||||
|
||||
public List<DatabaseEntry> getEntries() {
|
||||
return _entries.getList();
|
||||
}
|
||||
|
||||
public DatabaseEntry getEntryByUUID(UUID uuid) {
|
||||
return _entries.getByUUID(uuid);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
package com.beemdevelopment.aegis.db;
|
||||
|
||||
import com.beemdevelopment.aegis.encoding.Base64;
|
||||
import com.beemdevelopment.aegis.encoding.Base64Exception;
|
||||
import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
|
||||
import com.beemdevelopment.aegis.otp.OtpInfo;
|
||||
import com.beemdevelopment.aegis.otp.OtpInfoException;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public class DatabaseEntry implements Serializable {
|
||||
private UUID _uuid;
|
||||
private String _name = "";
|
||||
private String _issuer = "";
|
||||
private String _group;
|
||||
private OtpInfo _info;
|
||||
private byte[] _icon;
|
||||
|
||||
private DatabaseEntry(UUID uuid, OtpInfo info) {
|
||||
_uuid = uuid;
|
||||
_info = info;
|
||||
}
|
||||
|
||||
public DatabaseEntry(OtpInfo info) {
|
||||
this(UUID.randomUUID(), info);
|
||||
}
|
||||
|
||||
public DatabaseEntry(OtpInfo info, String name, String issuer) {
|
||||
this(info);
|
||||
setName(name);
|
||||
setIssuer(issuer);
|
||||
}
|
||||
|
||||
public DatabaseEntry(GoogleAuthInfo info) {
|
||||
this(info.getOtpInfo(), info.getAccountName(), info.getIssuer());
|
||||
}
|
||||
|
||||
public JSONObject toJson() {
|
||||
JSONObject obj = new JSONObject();
|
||||
|
||||
try {
|
||||
obj.put("type", _info.getType());
|
||||
obj.put("uuid", _uuid.toString());
|
||||
obj.put("name", _name);
|
||||
obj.put("issuer", _issuer);
|
||||
obj.put("group", _group);
|
||||
obj.put("icon", _icon == null ? JSONObject.NULL : Base64.encode(_icon));
|
||||
obj.put("info", _info.toJson());
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
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();
|
||||
} else {
|
||||
uuid = UUID.fromString(obj.getString("uuid"));
|
||||
}
|
||||
|
||||
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"));
|
||||
entry.setGroup(obj.optString("group", null));
|
||||
|
||||
Object icon = obj.get("icon");
|
||||
if (icon != JSONObject.NULL) {
|
||||
entry.setIcon(Base64.decode((String) icon));
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
public void resetUUID() {
|
||||
_uuid = UUID.randomUUID();
|
||||
}
|
||||
|
||||
public UUID getUUID() {
|
||||
return _uuid;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return _name;
|
||||
}
|
||||
|
||||
public String getIssuer() {
|
||||
return _issuer;
|
||||
}
|
||||
|
||||
public String getGroup() {
|
||||
return _group;
|
||||
}
|
||||
|
||||
public byte[] getIcon() {
|
||||
return _icon;
|
||||
}
|
||||
|
||||
public OtpInfo getInfo() {
|
||||
return _info;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
_name = name;
|
||||
}
|
||||
|
||||
public void setIssuer(String issuer) {
|
||||
_issuer = issuer;
|
||||
}
|
||||
|
||||
public void setGroup(String group) {
|
||||
_group = group;
|
||||
}
|
||||
|
||||
public void setInfo(OtpInfo info) {
|
||||
_info = info;
|
||||
}
|
||||
|
||||
public void setIcon(byte[] icon) {
|
||||
_icon = icon;
|
||||
}
|
||||
|
||||
public boolean hasIcon() {
|
||||
return _icon != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof DatabaseEntry)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DatabaseEntry entry = (DatabaseEntry) o;
|
||||
return getUUID().equals(entry.getUUID())
|
||||
&& getName().equals(entry.getName())
|
||||
&& getIssuer().equals(entry.getIssuer())
|
||||
&& Objects.equals(getGroup(), entry.getGroup())
|
||||
&& getInfo().equals(entry.getInfo())
|
||||
&& Arrays.equals(getIcon(), entry.getIcon());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package com.beemdevelopment.aegis.db;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class DatabaseEntryList implements Iterable<DatabaseEntry>, Serializable {
|
||||
private List<DatabaseEntry> _entries = new ArrayList<>();
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Iterator<DatabaseEntry> iterator() {
|
||||
return _entries.iterator();
|
||||
}
|
||||
|
||||
public void add(DatabaseEntry entry) {
|
||||
if (getByUUID(entry.getUUID()) != null) {
|
||||
throw new AssertionError("entry found with the same uuid");
|
||||
}
|
||||
_entries.add(entry);
|
||||
}
|
||||
|
||||
public void remove(DatabaseEntry entry) {
|
||||
entry = mustGetByUUID(entry.getUUID());
|
||||
_entries.remove(entry);
|
||||
}
|
||||
|
||||
public void replace(DatabaseEntry newEntry) {
|
||||
DatabaseEntry oldEntry = mustGetByUUID(newEntry.getUUID());
|
||||
_entries.set(_entries.indexOf(oldEntry), newEntry);
|
||||
}
|
||||
|
||||
public void swap(DatabaseEntry entry1, DatabaseEntry entry2) {
|
||||
Collections.swap(_entries, _entries.indexOf(entry1), _entries.indexOf(entry2));
|
||||
}
|
||||
|
||||
public List<DatabaseEntry> getList() {
|
||||
return Collections.unmodifiableList(_entries);
|
||||
}
|
||||
|
||||
public DatabaseEntry getByUUID(UUID uuid) {
|
||||
for (DatabaseEntry entry : _entries) {
|
||||
if (entry.getUUID().equals(uuid)) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private DatabaseEntry mustGetByUUID(UUID uuid) {
|
||||
DatabaseEntry entry = getByUUID(uuid);
|
||||
if (entry == null) {
|
||||
throw new AssertionError("no entry found with the same uuid");
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package com.beemdevelopment.aegis.db;
|
||||
|
||||
public class DatabaseException extends Exception {
|
||||
public DatabaseException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public DatabaseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
167
app/src/main/java/com/beemdevelopment/aegis/db/DatabaseFile.java
Normal file
167
app/src/main/java/com/beemdevelopment/aegis/db/DatabaseFile.java
Normal file
|
@ -0,0 +1,167 @@
|
|||
package com.beemdevelopment.aegis.db;
|
||||
|
||||
import com.beemdevelopment.aegis.crypto.CryptParameters;
|
||||
import com.beemdevelopment.aegis.crypto.CryptResult;
|
||||
import com.beemdevelopment.aegis.crypto.MasterKeyException;
|
||||
import com.beemdevelopment.aegis.db.slots.SlotList;
|
||||
import com.beemdevelopment.aegis.db.slots.SlotListException;
|
||||
import com.beemdevelopment.aegis.encoding.Base64;
|
||||
import com.beemdevelopment.aegis.encoding.Base64Exception;
|
||||
import com.beemdevelopment.aegis.encoding.HexException;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
public class DatabaseFile {
|
||||
public static final byte VERSION = 1;
|
||||
|
||||
private Object _content;
|
||||
private Header _header;
|
||||
|
||||
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 {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("version", VERSION);
|
||||
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) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static DatabaseFile fromJson(JSONObject obj) throws DatabaseFileException {
|
||||
try {
|
||||
if (obj.getInt("version") > VERSION) {
|
||||
throw new DatabaseFileException("unsupported version");
|
||||
}
|
||||
|
||||
Header header = Header.fromJson(obj.getJSONObject("header"));
|
||||
if (!header.isEmpty()) {
|
||||
return new DatabaseFile(obj.getString("db"), header);
|
||||
}
|
||||
|
||||
return new DatabaseFile(obj.getJSONObject("db"), header);
|
||||
} catch (JSONException e) {
|
||||
throw new DatabaseFileException(e);
|
||||
}
|
||||
}
|
||||
|
||||
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(DatabaseFileCredentials creds) throws DatabaseFileException {
|
||||
try {
|
||||
byte[] bytes = Base64.decode((String) _content);
|
||||
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 obj) {
|
||||
_content = obj;
|
||||
_header = new Header(null, null);
|
||||
}
|
||||
|
||||
public void setContent(JSONObject obj, DatabaseFileCredentials creds) throws DatabaseFileException {
|
||||
try {
|
||||
String string = obj.toString(4);
|
||||
byte[] dbBytes = string.getBytes("UTF-8");
|
||||
|
||||
CryptResult result = creds.encrypt(dbBytes);
|
||||
_content = Base64.encode(result.getData());
|
||||
_header = new Header(creds.getSlots(), result.getParams());
|
||||
} catch (MasterKeyException | UnsupportedEncodingException | JSONException e) {
|
||||
throw new DatabaseFileException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Header {
|
||||
private SlotList _slots;
|
||||
private CryptParameters _params;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package com.beemdevelopment.aegis.db;
|
||||
|
||||
import com.beemdevelopment.aegis.crypto.CryptParameters;
|
||||
import com.beemdevelopment.aegis.crypto.CryptResult;
|
||||
import com.beemdevelopment.aegis.crypto.MasterKey;
|
||||
import com.beemdevelopment.aegis.crypto.MasterKeyException;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import com.beemdevelopment.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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package com.beemdevelopment.aegis.db;
|
||||
|
||||
public class DatabaseFileException extends Exception {
|
||||
public DatabaseFileException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public DatabaseFileException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,236 @@
|
|||
package com.beemdevelopment.aegis.db;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Environment;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.text.Collator;
|
||||
import java.util.List;
|
||||
import java.util.TreeSet;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.beemdevelopment.aegis.BuildConfig;
|
||||
import com.beemdevelopment.aegis.R;
|
||||
|
||||
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 Database _db;
|
||||
private DatabaseFile _file;
|
||||
private DatabaseFileCredentials _creds;
|
||||
private boolean _encrypt;
|
||||
|
||||
private Context _context;
|
||||
|
||||
public DatabaseManager(Context context) {
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public boolean fileExists() {
|
||||
File file = new File(_context.getFilesDir(), FILENAME);
|
||||
return file.exists() && file.isFile();
|
||||
}
|
||||
|
||||
public void load() throws DatabaseManagerException {
|
||||
assertState(true, false);
|
||||
|
||||
try (FileInputStream file = _context.openFileInput(FILENAME)) {
|
||||
byte[] fileBytes = new byte[(int) file.getChannel().size()];
|
||||
DataInputStream stream = new DataInputStream(file);
|
||||
stream.readFully(fileBytes);
|
||||
stream.close();
|
||||
|
||||
_file = DatabaseFile.fromBytes(fileBytes);
|
||||
_encrypt = _file.isEncrypted();
|
||||
if (!isEncryptionEnabled()) {
|
||||
JSONObject obj = _file.getContent();
|
||||
_db = Database.fromJson(obj);
|
||||
}
|
||||
} catch (IOException | DatabaseFileException | DatabaseException e) {
|
||||
throw new DatabaseManagerException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void lock() {
|
||||
assertState(false, true);
|
||||
_creds = null;
|
||||
_db = null;
|
||||
}
|
||||
|
||||
public void unlock(DatabaseFileCredentials creds) throws DatabaseManagerException {
|
||||
assertState(true, true);
|
||||
|
||||
try {
|
||||
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 {
|
||||
byte[] bytes = file.toBytes();
|
||||
try (FileOutputStream stream = context.openFileOutput(FILENAME, Context.MODE_PRIVATE)) {
|
||||
stream.write(bytes);
|
||||
} catch (IOException e) {
|
||||
throw new DatabaseManagerException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void save() throws DatabaseManagerException {
|
||||
assertState(false, true);
|
||||
|
||||
try {
|
||||
JSONObject obj = _db.toJson();
|
||||
if (isEncryptionEnabled()) {
|
||||
_file.setContent(obj, _creds);
|
||||
} else {
|
||||
_file.setContent(obj);
|
||||
}
|
||||
save(_context, _file);
|
||||
} catch (DatabaseFileException e) {
|
||||
throw new DatabaseManagerException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public String export(boolean encrypt) throws DatabaseManagerException {
|
||||
assertState(false, true);
|
||||
|
||||
try {
|
||||
DatabaseFile dbFile = new DatabaseFile();
|
||||
if (encrypt && isEncryptionEnabled()) {
|
||||
dbFile.setContent(_db.toJson(), _creds);
|
||||
} else {
|
||||
dbFile.setContent(_db.toJson());
|
||||
}
|
||||
|
||||
String dirName = !BuildConfig.DEBUG ? _context.getString(R.string.app_name) : _context.getString(R.string.app_name_dev);
|
||||
File dir = new File(Environment.getExternalStorageDirectory(), dirName);
|
||||
if (!dir.exists() && !dir.mkdirs()) {
|
||||
throw new IOException("error creating external storage directory");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return file.getAbsolutePath();
|
||||
} catch (IOException | DatabaseFileException e) {
|
||||
throw new DatabaseManagerException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void addEntry(DatabaseEntry entry) {
|
||||
assertState(false, true);
|
||||
_db.addEntry(entry);
|
||||
}
|
||||
|
||||
public void removeEntry(DatabaseEntry entry) {
|
||||
assertState(false, true);
|
||||
_db.removeEntry(entry);
|
||||
}
|
||||
|
||||
public void replaceEntry(DatabaseEntry entry) {
|
||||
assertState(false, true);
|
||||
_db.replaceEntry(entry);
|
||||
}
|
||||
|
||||
public void swapEntries(DatabaseEntry entry1, DatabaseEntry entry2) {
|
||||
assertState(false, true);
|
||||
_db.swapEntries(entry1, entry2);
|
||||
}
|
||||
|
||||
public List<DatabaseEntry> getEntries() {
|
||||
assertState(false, true);
|
||||
return _db.getEntries();
|
||||
}
|
||||
|
||||
public DatabaseEntry getEntryByUUID(UUID uuid) {
|
||||
assertState(false, true);
|
||||
return _db.getEntryByUUID(uuid);
|
||||
}
|
||||
|
||||
public TreeSet<String> getGroups() {
|
||||
assertState(false, true);
|
||||
|
||||
TreeSet<String> groups = new TreeSet<>(Collator.getInstance());
|
||||
for (DatabaseEntry entry : getEntries()) {
|
||||
String group = entry.getGroup();
|
||||
if (group != null) {
|
||||
groups.add(group);
|
||||
}
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
public DatabaseFileCredentials getCredentials() {
|
||||
assertState(false, true);
|
||||
return _creds;
|
||||
}
|
||||
|
||||
public void setCredentials(DatabaseFileCredentials creds) {
|
||||
assertState(false, true);
|
||||
_creds = creds;
|
||||
}
|
||||
|
||||
public DatabaseFile.Header getFileHeader() {
|
||||
assertLoaded(true);
|
||||
return _file.getHeader();
|
||||
}
|
||||
|
||||
public boolean isEncryptionEnabled() {
|
||||
assertLoaded(true);
|
||||
return _encrypt;
|
||||
}
|
||||
|
||||
public void enableEncryption(DatabaseFileCredentials creds) throws DatabaseManagerException {
|
||||
assertState(false, true);
|
||||
_creds = creds;
|
||||
_encrypt = true;
|
||||
save();
|
||||
}
|
||||
|
||||
public void disableEncryption() throws DatabaseManagerException {
|
||||
assertState(false, true);
|
||||
_creds = null;
|
||||
_encrypt = false;
|
||||
save();
|
||||
}
|
||||
|
||||
public boolean isLoaded() {
|
||||
return _file != null;
|
||||
}
|
||||
|
||||
public boolean isLocked() {
|
||||
return _db == null;
|
||||
}
|
||||
|
||||
private void assertState(boolean locked, boolean 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 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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package com.beemdevelopment.aegis.db;
|
||||
|
||||
public class DatabaseManagerException extends Exception {
|
||||
public DatabaseManagerException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package com.beemdevelopment.aegis.db.slots;
|
||||
|
||||
import com.beemdevelopment.aegis.crypto.CryptParameters;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class FingerprintSlot extends RawSlot {
|
||||
public FingerprintSlot() {
|
||||
super();
|
||||
}
|
||||
|
||||
FingerprintSlot(UUID uuid, byte[] key, CryptParameters keyParams) {
|
||||
super(uuid, key, keyParams);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getType() {
|
||||
return TYPE_FINGERPRINT;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package com.beemdevelopment.aegis.db.slots;
|
||||
|
||||
import com.beemdevelopment.aegis.crypto.CryptParameters;
|
||||
import com.beemdevelopment.aegis.crypto.CryptoUtils;
|
||||
import com.beemdevelopment.aegis.crypto.SCryptParameters;
|
||||
import com.beemdevelopment.aegis.encoding.Hex;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
public class PasswordSlot extends RawSlot {
|
||||
private SCryptParameters _params;
|
||||
|
||||
public PasswordSlot() {
|
||||
super();
|
||||
}
|
||||
|
||||
protected PasswordSlot(UUID uuid, byte[] key, CryptParameters keyParams, SCryptParameters scryptParams) {
|
||||
super(uuid, key, keyParams);
|
||||
_params = scryptParams;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject toJson() {
|
||||
try {
|
||||
JSONObject obj = super.toJson();
|
||||
obj.put("n", _params.getN());
|
||||
obj.put("r", _params.getR());
|
||||
obj.put("p", _params.getP());
|
||||
obj.put("salt", Hex.encode(_params.getSalt()));
|
||||
return obj;
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public SecretKey deriveKey(char[] password, SCryptParameters params) {
|
||||
SecretKey key = CryptoUtils.deriveKey(password, params);
|
||||
_params = params;
|
||||
return key;
|
||||
}
|
||||
|
||||
public SecretKey deriveKey(char[] password) {
|
||||
return CryptoUtils.deriveKey(password, _params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getType() {
|
||||
return TYPE_DERIVED;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package com.beemdevelopment.aegis.db.slots;
|
||||
|
||||
import com.beemdevelopment.aegis.crypto.CryptParameters;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class RawSlot extends Slot {
|
||||
public RawSlot() {
|
||||
super();
|
||||
}
|
||||
|
||||
protected RawSlot(UUID uuid, byte[] key, CryptParameters keyParams) {
|
||||
super(uuid, key, keyParams);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getType() {
|
||||
return TYPE_RAW;
|
||||
}
|
||||
}
|
152
app/src/main/java/com/beemdevelopment/aegis/db/slots/Slot.java
Normal file
152
app/src/main/java/com/beemdevelopment/aegis/db/slots/Slot.java
Normal file
|
@ -0,0 +1,152 @@
|
|||
package com.beemdevelopment.aegis.db.slots;
|
||||
|
||||
import com.beemdevelopment.aegis.crypto.CryptParameters;
|
||||
import com.beemdevelopment.aegis.crypto.CryptResult;
|
||||
import com.beemdevelopment.aegis.crypto.CryptoUtils;
|
||||
import com.beemdevelopment.aegis.crypto.MasterKey;
|
||||
import com.beemdevelopment.aegis.crypto.SCryptParameters;
|
||||
import com.beemdevelopment.aegis.encoding.Hex;
|
||||
import com.beemdevelopment.aegis.encoding.HexException;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
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;
|
||||
|
||||
private UUID _uuid;
|
||||
private byte[] _encryptedMasterKey;
|
||||
private CryptParameters _encryptedMasterKeyParams;
|
||||
|
||||
protected Slot() {
|
||||
_uuid = UUID.randomUUID();
|
||||
}
|
||||
|
||||
protected Slot(UUID uuid, byte[] key, CryptParameters keyParams) {
|
||||
_uuid = uuid;
|
||||
_encryptedMasterKey = key;
|
||||
_encryptedMasterKeyParams = keyParams;
|
||||
}
|
||||
|
||||
// getKey decrypts the encrypted master key in this slot using the given cipher and returns it.
|
||||
public MasterKey getKey(Cipher cipher) throws SlotException, SlotIntegrityException {
|
||||
try {
|
||||
CryptResult res = CryptoUtils.decrypt(_encryptedMasterKey, cipher, _encryptedMasterKeyParams);
|
||||
SecretKey key = new SecretKeySpec(res.getData(), CryptoUtils.CRYPTO_AEAD);
|
||||
return new MasterKey(key);
|
||||
} catch (BadPaddingException e) {
|
||||
throw new SlotIntegrityException(e);
|
||||
} catch (IOException | IllegalBlockSizeException e) {
|
||||
throw new SlotException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// setKey encrypts the given master key using the given cipher and stores the result in this slot.
|
||||
public void setKey(MasterKey masterKey, Cipher cipher) throws SlotException {
|
||||
try {
|
||||
byte[] masterKeyBytes = masterKey.getBytes();
|
||||
CryptResult res = CryptoUtils.encrypt(masterKeyBytes, cipher);
|
||||
_encryptedMasterKey = res.getData();
|
||||
_encryptedMasterKeyParams = res.getParams();
|
||||
} catch (BadPaddingException | IllegalBlockSizeException e) {
|
||||
throw new SlotException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Cipher createEncryptCipher(SecretKey key) throws SlotException {
|
||||
try {
|
||||
return CryptoUtils.createEncryptCipher(key);
|
||||
} catch (InvalidAlgorithmParameterException
|
||||
| NoSuchPaddingException
|
||||
| NoSuchAlgorithmException
|
||||
| InvalidKeyException e) {
|
||||
throw new SlotException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Cipher createDecryptCipher(SecretKey key) throws SlotException {
|
||||
try {
|
||||
return CryptoUtils.createDecryptCipher(key, _encryptedMasterKeyParams.getNonce());
|
||||
} catch (InvalidAlgorithmParameterException
|
||||
| NoSuchAlgorithmException
|
||||
| InvalidKeyException
|
||||
| NoSuchPaddingException e) {
|
||||
throw new SlotException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public JSONObject toJson() {
|
||||
try {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("type", getType());
|
||||
obj.put("uuid", _uuid.toString());
|
||||
obj.put("key", Hex.encode(_encryptedMasterKey));
|
||||
obj.put("key_params", _encryptedMasterKeyParams.toJson());
|
||||
return obj;
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Slot fromJson(JSONObject obj) throws SlotException {
|
||||
Slot slot;
|
||||
|
||||
try {
|
||||
UUID uuid;
|
||||
if (!obj.has("uuid")) {
|
||||
uuid = UUID.randomUUID();
|
||||
} else {
|
||||
uuid = UUID.fromString(obj.getString("uuid"));
|
||||
}
|
||||
|
||||
byte[] key = Hex.decode(obj.getString("key"));
|
||||
CryptParameters keyParams = CryptParameters.fromJson(obj.getJSONObject("key_params"));
|
||||
|
||||
switch (obj.getInt("type")) {
|
||||
case Slot.TYPE_RAW:
|
||||
slot = new RawSlot(uuid, key, keyParams);
|
||||
break;
|
||||
case Slot.TYPE_DERIVED:
|
||||
SCryptParameters scryptParams = new SCryptParameters(
|
||||
obj.getInt("n"),
|
||||
obj.getInt("r"),
|
||||
obj.getInt("p"),
|
||||
Hex.decode(obj.getString("salt"))
|
||||
);
|
||||
slot = new PasswordSlot(uuid, key, keyParams, scryptParams);
|
||||
break;
|
||||
case Slot.TYPE_FINGERPRINT:
|
||||
slot = new FingerprintSlot(uuid, key, keyParams);
|
||||
break;
|
||||
default:
|
||||
throw new SlotException("unrecognized slot type");
|
||||
}
|
||||
} catch (JSONException | HexException e) {
|
||||
throw new SlotException(e);
|
||||
}
|
||||
|
||||
return slot;
|
||||
}
|
||||
|
||||
public abstract byte getType();
|
||||
|
||||
public UUID getUUID() {
|
||||
return _uuid;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package com.beemdevelopment.aegis.db.slots;
|
||||
|
||||
public class SlotException extends Exception {
|
||||
public SlotException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public SlotException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package com.beemdevelopment.aegis.db.slots;
|
||||
|
||||
public class SlotIntegrityException extends Exception {
|
||||
public SlotIntegrityException() {
|
||||
|
||||
}
|
||||
|
||||
public SlotIntegrityException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package com.beemdevelopment.aegis.db.slots;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class SlotList implements Iterable<Slot>, Serializable {
|
||||
private List<Slot> _slots = new ArrayList<>();
|
||||
|
||||
public JSONArray toJson() {
|
||||
JSONArray array = new JSONArray();
|
||||
for (Slot slot : this) {
|
||||
array.put(slot.toJson());
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
public static SlotList fromJson(JSONArray array) throws SlotListException {
|
||||
SlotList slots = new SlotList();
|
||||
|
||||
try {
|
||||
for (int i = 0; i < array.length(); i++) {
|
||||
JSONObject obj = array.getJSONObject(i);
|
||||
Slot slot = Slot.fromJson(obj);
|
||||
slots.add(slot);
|
||||
}
|
||||
} catch (SlotException | JSONException e) {
|
||||
throw new SlotListException(e);
|
||||
}
|
||||
|
||||
return slots;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public void remove(Slot slot) {
|
||||
_slots.remove(slot);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return _slots.size();
|
||||
}
|
||||
|
||||
public <T extends Slot> T find(Class<T> type) {
|
||||
for (Slot slot : this) {
|
||||
if (slot.getClass() == type) {
|
||||
return type.cast(slot);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public <T extends Slot> List<T> findAll(Class<T> type) {
|
||||
ArrayList<T> list = new ArrayList<>();
|
||||
for (Slot slot : this) {
|
||||
if (slot.getClass() == type) {
|
||||
list.add(type.cast(slot));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public <T extends Slot> boolean has(Class<T> type) {
|
||||
return find(type) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Slot> iterator() {
|
||||
return _slots.iterator();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package com.beemdevelopment.aegis.db.slots;
|
||||
|
||||
public class SlotListException extends Exception {
|
||||
public SlotListException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public SlotListException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue