mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-05-04 20:30:36 +00:00
Refactor the database classes to be more reusable
This commit is contained in:
parent
571cf20eda
commit
0434513820
18 changed files with 311 additions and 256 deletions
|
@ -30,7 +30,7 @@ public class CryptParameters implements Serializable {
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CryptParameters parseJson(JSONObject obj) throws JSONException, HexException {
|
public static CryptParameters fromJson(JSONObject obj) throws JSONException, HexException {
|
||||||
byte[] nonce = Hex.decode(obj.getString("nonce"));
|
byte[] nonce = Hex.decode(obj.getString("nonce"));
|
||||||
byte[] tag = Hex.decode(obj.getString("tag"));
|
byte[] tag = Hex.decode(obj.getString("tag"));
|
||||||
return new CryptParameters(nonce, tag);
|
return new CryptParameters(nonce, tag);
|
||||||
|
|
|
@ -12,14 +12,13 @@ import me.impy.aegis.otp.OtpInfoException;
|
||||||
|
|
||||||
public class Database {
|
public class Database {
|
||||||
private static final int VERSION = 1;
|
private static final int VERSION = 1;
|
||||||
|
|
||||||
private DatabaseEntryList _entries = new DatabaseEntryList();
|
private DatabaseEntryList _entries = new DatabaseEntryList();
|
||||||
|
|
||||||
public JSONObject serialize() {
|
public JSONObject toJson() {
|
||||||
try {
|
try {
|
||||||
JSONArray array = new JSONArray();
|
JSONArray array = new JSONArray();
|
||||||
for (DatabaseEntry e : _entries) {
|
for (DatabaseEntry e : _entries) {
|
||||||
array.put(e.serialize());
|
array.put(e.toJson());
|
||||||
}
|
}
|
||||||
|
|
||||||
JSONObject obj = new JSONObject();
|
JSONObject obj = new JSONObject();
|
||||||
|
@ -31,8 +30,9 @@ public class Database {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deserialize(JSONObject obj) throws DatabaseException {
|
public static Database fromJson(JSONObject obj) throws DatabaseException {
|
||||||
// TODO: support different VERSION deserialization providers
|
Database db = new Database();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
int ver = obj.getInt("version");
|
int ver = obj.getInt("version");
|
||||||
if (ver != VERSION) {
|
if (ver != VERSION) {
|
||||||
|
@ -41,13 +41,14 @@ public class Database {
|
||||||
|
|
||||||
JSONArray array = obj.getJSONArray("entries");
|
JSONArray array = obj.getJSONArray("entries");
|
||||||
for (int i = 0; i < array.length(); i++) {
|
for (int i = 0; i < array.length(); i++) {
|
||||||
DatabaseEntry entry = new DatabaseEntry(null);
|
DatabaseEntry entry = DatabaseEntry.fromJson(array.getJSONObject(i));
|
||||||
entry.deserialize(array.getJSONObject(i));
|
db.addEntry(entry);
|
||||||
addEntry(entry);
|
|
||||||
}
|
}
|
||||||
} catch (Base64Exception | OtpInfoException | JSONException e) {
|
} catch (Base64Exception | OtpInfoException | JSONException e) {
|
||||||
throw new DatabaseException(e);
|
throw new DatabaseException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addEntry(DatabaseEntry entry) {
|
public void addEntry(DatabaseEntry entry) {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import java.util.UUID;
|
||||||
|
|
||||||
import me.impy.aegis.encoding.Base64;
|
import me.impy.aegis.encoding.Base64;
|
||||||
import me.impy.aegis.encoding.Base64Exception;
|
import me.impy.aegis.encoding.Base64Exception;
|
||||||
|
import me.impy.aegis.otp.GoogleAuthInfo;
|
||||||
import me.impy.aegis.otp.OtpInfo;
|
import me.impy.aegis.otp.OtpInfo;
|
||||||
import me.impy.aegis.otp.OtpInfoException;
|
import me.impy.aegis.otp.OtpInfoException;
|
||||||
|
|
||||||
|
@ -19,9 +20,13 @@ public class DatabaseEntry implements Serializable {
|
||||||
private OtpInfo _info;
|
private OtpInfo _info;
|
||||||
private byte[] _icon;
|
private byte[] _icon;
|
||||||
|
|
||||||
public DatabaseEntry(OtpInfo info) {
|
private DatabaseEntry(UUID uuid, OtpInfo info) {
|
||||||
|
_uuid = uuid;
|
||||||
_info = info;
|
_info = info;
|
||||||
_uuid = UUID.randomUUID();
|
}
|
||||||
|
|
||||||
|
public DatabaseEntry(OtpInfo info) {
|
||||||
|
this(UUID.randomUUID(), info);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DatabaseEntry(OtpInfo info, String name, String issuer) {
|
public DatabaseEntry(OtpInfo info, String name, String issuer) {
|
||||||
|
@ -30,7 +35,11 @@ public class DatabaseEntry implements Serializable {
|
||||||
setIssuer(issuer);
|
setIssuer(issuer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public JSONObject serialize() {
|
public DatabaseEntry(GoogleAuthInfo info) {
|
||||||
|
this(info.getOtpInfo(), info.getAccountName(), info.getIssuer());
|
||||||
|
}
|
||||||
|
|
||||||
|
public JSONObject toJson() {
|
||||||
JSONObject obj = new JSONObject();
|
JSONObject obj = new JSONObject();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -47,22 +56,26 @@ public class DatabaseEntry implements Serializable {
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deserialize(JSONObject obj) throws JSONException, OtpInfoException, Base64Exception {
|
public static DatabaseEntry fromJson(JSONObject obj) throws JSONException, OtpInfoException, Base64Exception {
|
||||||
// if there is no uuid, generate a new one
|
// if there is no uuid, generate a new one
|
||||||
|
UUID uuid;
|
||||||
if (!obj.has("uuid")) {
|
if (!obj.has("uuid")) {
|
||||||
_uuid = UUID.randomUUID();
|
uuid = UUID.randomUUID();
|
||||||
} else {
|
} else {
|
||||||
_uuid = UUID.fromString(obj.getString("uuid"));
|
uuid = UUID.fromString(obj.getString("uuid"));
|
||||||
}
|
}
|
||||||
_name = obj.getString("name");
|
|
||||||
_issuer = obj.getString("issuer");
|
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"));
|
||||||
|
|
||||||
Object icon = obj.get("icon");
|
Object icon = obj.get("icon");
|
||||||
if (icon != JSONObject.NULL) {
|
if (icon != JSONObject.NULL) {
|
||||||
_icon = Base64.decode((String) icon);
|
entry.setIcon(Base64.decode((String) icon));
|
||||||
}
|
}
|
||||||
|
|
||||||
_info = OtpInfo.parseJson(obj.getString("type"), obj.getJSONObject("info"));
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resetUUID() {
|
public void resetUUID() {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package me.impy.aegis.db;
|
package me.impy.aegis.db;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
@ -8,7 +7,6 @@ import java.io.UnsupportedEncodingException;
|
||||||
|
|
||||||
import me.impy.aegis.crypto.CryptParameters;
|
import me.impy.aegis.crypto.CryptParameters;
|
||||||
import me.impy.aegis.crypto.CryptResult;
|
import me.impy.aegis.crypto.CryptResult;
|
||||||
import me.impy.aegis.crypto.MasterKey;
|
|
||||||
import me.impy.aegis.crypto.MasterKeyException;
|
import me.impy.aegis.crypto.MasterKeyException;
|
||||||
import me.impy.aegis.db.slots.SlotList;
|
import me.impy.aegis.db.slots.SlotList;
|
||||||
import me.impy.aegis.db.slots.SlotListException;
|
import me.impy.aegis.db.slots.SlotListException;
|
||||||
|
@ -20,22 +18,41 @@ public class DatabaseFile {
|
||||||
public static final byte VERSION = 1;
|
public static final byte VERSION = 1;
|
||||||
|
|
||||||
private Object _content;
|
private Object _content;
|
||||||
private CryptParameters _cryptParameters;
|
private Header _header;
|
||||||
private SlotList _slots;
|
|
||||||
|
|
||||||
public byte[] serialize() {
|
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 {
|
try {
|
||||||
// don't write the crypt parameters and slots if the content is not encrypted
|
|
||||||
boolean plain = _content instanceof JSONObject || !isEncrypted();
|
|
||||||
JSONObject headerObj = new JSONObject();
|
|
||||||
headerObj.put("slots", plain ? JSONObject.NULL : SlotList.serialize(_slots));
|
|
||||||
headerObj.put("params", plain ? JSONObject.NULL : _cryptParameters.toJson());
|
|
||||||
|
|
||||||
JSONObject obj = new JSONObject();
|
JSONObject obj = new JSONObject();
|
||||||
obj.put("version", VERSION);
|
obj.put("version", VERSION);
|
||||||
obj.put("header", headerObj);
|
obj.put("header", _header.toJson());
|
||||||
obj.put("db", _content);
|
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);
|
String string = obj.toString(4);
|
||||||
return string.getBytes("UTF-8");
|
return string.getBytes("UTF-8");
|
||||||
} catch (JSONException | UnsupportedEncodingException e) {
|
} catch (JSONException | UnsupportedEncodingException e) {
|
||||||
|
@ -43,76 +60,108 @@ public class DatabaseFile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deserialize(byte[] data) throws DatabaseFileException {
|
public static DatabaseFile fromJson(JSONObject obj) throws DatabaseFileException {
|
||||||
try {
|
try {
|
||||||
JSONObject obj = new JSONObject(new String(data, "UTF-8"));
|
|
||||||
JSONObject headerObj = obj.getJSONObject("header");
|
|
||||||
if (obj.getInt("version") > VERSION) {
|
if (obj.getInt("version") > VERSION) {
|
||||||
throw new DatabaseFileException("unsupported version");
|
throw new DatabaseFileException("unsupported version");
|
||||||
}
|
}
|
||||||
|
|
||||||
JSONArray slotObj = headerObj.optJSONArray("slots");
|
Header header = Header.fromJson(obj.getJSONObject("header"));
|
||||||
if (slotObj != null) {
|
if (!header.isEmpty()) {
|
||||||
_slots = SlotList.deserialize(slotObj);
|
return new DatabaseFile(obj.getString("db"), header);
|
||||||
}
|
}
|
||||||
|
|
||||||
JSONObject cryptObj = headerObj.optJSONObject("params");
|
return new DatabaseFile(obj.getJSONObject("db"), header);
|
||||||
if (cryptObj != null) {
|
} catch (JSONException e) {
|
||||||
_cryptParameters = CryptParameters.parseJson(cryptObj);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cryptObj == null || slotObj == null) {
|
|
||||||
_content = obj.getJSONObject("db");
|
|
||||||
} else {
|
|
||||||
_content = obj.getString("db");
|
|
||||||
}
|
|
||||||
} catch (SlotListException | UnsupportedEncodingException | JSONException | HexException e) {
|
|
||||||
throw new DatabaseFileException(e);
|
throw new DatabaseFileException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEncrypted() {
|
public static DatabaseFile fromBytes(byte[] data) throws DatabaseFileException {
|
||||||
return _slots != null;
|
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() {
|
public JSONObject getContent() {
|
||||||
return (JSONObject) _content;
|
return (JSONObject) _content;
|
||||||
}
|
}
|
||||||
|
|
||||||
public JSONObject getContent(MasterKey key) throws DatabaseFileException {
|
public JSONObject getContent(DatabaseFileCredentials creds) throws DatabaseFileException {
|
||||||
try {
|
try {
|
||||||
byte[] bytes = Base64.decode((String) _content);
|
byte[] bytes = Base64.decode((String) _content);
|
||||||
CryptResult result = key.decrypt(bytes, _cryptParameters);
|
CryptResult result = creds.decrypt(bytes, _header.getParams());
|
||||||
return new JSONObject(new String(result.getData(), "UTF-8"));
|
return new JSONObject(new String(result.getData(), "UTF-8"));
|
||||||
} catch (MasterKeyException | JSONException | UnsupportedEncodingException | Base64Exception e) {
|
} catch (MasterKeyException | JSONException | UnsupportedEncodingException | Base64Exception e) {
|
||||||
throw new DatabaseFileException(e);
|
throw new DatabaseFileException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setContent(JSONObject dbObj) {
|
public void setContent(JSONObject obj) {
|
||||||
_content = dbObj;
|
_content = obj;
|
||||||
_cryptParameters = null;
|
_header = new Header(null, null);
|
||||||
_slots = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setContent(JSONObject dbObj, MasterKey key) throws DatabaseFileException {
|
public void setContent(JSONObject obj, DatabaseFileCredentials creds) throws DatabaseFileException {
|
||||||
try {
|
try {
|
||||||
String string = dbObj.toString(4);
|
String string = obj.toString(4);
|
||||||
byte[] dbBytes = string.getBytes("UTF-8");
|
byte[] dbBytes = string.getBytes("UTF-8");
|
||||||
|
|
||||||
CryptResult result = key.encrypt(dbBytes);
|
CryptResult result = creds.encrypt(dbBytes);
|
||||||
_content = Base64.encode(result.getData());
|
_content = Base64.encode(result.getData());
|
||||||
_cryptParameters = result.getParams();
|
_header = new Header(creds.getSlots(), result.getParams());
|
||||||
} catch (MasterKeyException | UnsupportedEncodingException | JSONException e) {
|
} catch (MasterKeyException | UnsupportedEncodingException | JSONException e) {
|
||||||
throw new DatabaseFileException(e);
|
throw new DatabaseFileException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public SlotList getSlots() {
|
public static class Header {
|
||||||
return _slots;
|
private SlotList _slots;
|
||||||
}
|
private CryptParameters _params;
|
||||||
|
|
||||||
public void setSlots(SlotList slots) {
|
public Header(SlotList slots, CryptParameters params) {
|
||||||
_slots = slots;
|
_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,40 @@
|
||||||
|
package me.impy.aegis.db;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import me.impy.aegis.crypto.CryptParameters;
|
||||||
|
import me.impy.aegis.crypto.CryptResult;
|
||||||
|
import me.impy.aegis.crypto.MasterKey;
|
||||||
|
import me.impy.aegis.crypto.MasterKeyException;
|
||||||
|
import me.impy.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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,17 +14,17 @@ import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import me.impy.aegis.BuildConfig;
|
import me.impy.aegis.BuildConfig;
|
||||||
import me.impy.aegis.crypto.MasterKey;
|
|
||||||
import me.impy.aegis.db.slots.SlotList;
|
|
||||||
|
|
||||||
public class DatabaseManager {
|
public class DatabaseManager {
|
||||||
private static final String FILENAME = "aegis.json";
|
private static final String FILENAME = "aegis.json";
|
||||||
private static final String FILENAME_EXPORT = "aegis_export.json";
|
private static final String FILENAME_EXPORT = "aegis_export.json";
|
||||||
private static final String FILENAME_EXPORT_PLAIN = "aegis_export_plain.json";
|
private static final String FILENAME_EXPORT_PLAIN = "aegis_export_plain.json";
|
||||||
|
|
||||||
private MasterKey _key;
|
|
||||||
private DatabaseFile _file;
|
|
||||||
private Database _db;
|
private Database _db;
|
||||||
|
private DatabaseFile _file;
|
||||||
|
private DatabaseFileCredentials _creds;
|
||||||
|
private boolean _encrypt;
|
||||||
|
|
||||||
private Context _context;
|
private Context _context;
|
||||||
|
|
||||||
public DatabaseManager(Context context) {
|
public DatabaseManager(Context context) {
|
||||||
|
@ -39,31 +39,17 @@ public class DatabaseManager {
|
||||||
public void load() throws DatabaseManagerException {
|
public void load() throws DatabaseManagerException {
|
||||||
assertState(true, false);
|
assertState(true, false);
|
||||||
|
|
||||||
try {
|
try (FileInputStream file = _context.openFileInput(FILENAME)) {
|
||||||
byte[] fileBytes;
|
byte[] fileBytes = new byte[(int) file.getChannel().size()];
|
||||||
FileInputStream file = null;
|
DataInputStream stream = new DataInputStream(file);
|
||||||
|
stream.readFully(fileBytes);
|
||||||
|
stream.close();
|
||||||
|
|
||||||
try {
|
_file = DatabaseFile.fromBytes(fileBytes);
|
||||||
file = _context.openFileInput(FILENAME);
|
_encrypt = _file.isEncrypted();
|
||||||
fileBytes = new byte[(int) file.getChannel().size()];
|
if (!isEncryptionEnabled()) {
|
||||||
DataInputStream stream = new DataInputStream(file);
|
|
||||||
stream.readFully(fileBytes);
|
|
||||||
stream.close();
|
|
||||||
} finally {
|
|
||||||
// always close the file stream
|
|
||||||
// there is no need to close the DataInputStream
|
|
||||||
if (file != null) {
|
|
||||||
file.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_file = new DatabaseFile();
|
|
||||||
_file.deserialize(fileBytes);
|
|
||||||
|
|
||||||
if (!_file.isEncrypted()) {
|
|
||||||
JSONObject obj = _file.getContent();
|
JSONObject obj = _file.getContent();
|
||||||
_db = new Database();
|
_db = Database.fromJson(obj);
|
||||||
_db.deserialize(obj);
|
|
||||||
}
|
}
|
||||||
} catch (IOException | DatabaseFileException | DatabaseException e) {
|
} catch (IOException | DatabaseFileException | DatabaseException e) {
|
||||||
throw new DatabaseManagerException(e);
|
throw new DatabaseManagerException(e);
|
||||||
|
@ -73,37 +59,26 @@ public class DatabaseManager {
|
||||||
public void lock() {
|
public void lock() {
|
||||||
assertState(false, true);
|
assertState(false, true);
|
||||||
// TODO: properly clear everything
|
// TODO: properly clear everything
|
||||||
_key = null;
|
_creds = null;
|
||||||
_db = null;
|
_db = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unlock(MasterKey key) throws DatabaseManagerException {
|
public void unlock(DatabaseFileCredentials creds) throws DatabaseManagerException {
|
||||||
assertState(true, true);
|
assertState(true, true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
JSONObject obj = _file.getContent(key);
|
JSONObject obj = _file.getContent(creds);
|
||||||
_db = new Database();
|
_db = Database.fromJson(obj);
|
||||||
_db.deserialize(obj);
|
_creds = creds;
|
||||||
_key = key;
|
|
||||||
} catch (DatabaseFileException | DatabaseException e) {
|
} catch (DatabaseFileException | DatabaseException e) {
|
||||||
throw new DatabaseManagerException(e);
|
throw new DatabaseManagerException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void save(Context context, DatabaseFile file) throws DatabaseManagerException {
|
public static void save(Context context, DatabaseFile file) throws DatabaseManagerException {
|
||||||
try {
|
byte[] bytes = file.toBytes();
|
||||||
byte[] bytes = file.serialize();
|
try (FileOutputStream stream = context.openFileOutput(FILENAME, Context.MODE_PRIVATE)) {
|
||||||
|
stream.write(bytes);
|
||||||
FileOutputStream stream = null;
|
|
||||||
try {
|
|
||||||
stream = context.openFileOutput(FILENAME, Context.MODE_PRIVATE);
|
|
||||||
stream.write(bytes);
|
|
||||||
} finally {
|
|
||||||
// always close the file stream
|
|
||||||
if (stream != null) {
|
|
||||||
stream.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new DatabaseManagerException(e);
|
throw new DatabaseManagerException(e);
|
||||||
}
|
}
|
||||||
|
@ -113,9 +88,9 @@ public class DatabaseManager {
|
||||||
assertState(false, true);
|
assertState(false, true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
JSONObject obj = _db.serialize();
|
JSONObject obj = _db.toJson();
|
||||||
if (_file.isEncrypted()) {
|
if (isEncryptionEnabled()) {
|
||||||
_file.setContent(obj, _key);
|
_file.setContent(obj, _creds);
|
||||||
} else {
|
} else {
|
||||||
_file.setContent(obj);
|
_file.setContent(obj);
|
||||||
}
|
}
|
||||||
|
@ -130,31 +105,22 @@ public class DatabaseManager {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
DatabaseFile dbFile = new DatabaseFile();
|
DatabaseFile dbFile = new DatabaseFile();
|
||||||
dbFile.setSlots(_file.getSlots());
|
if (encrypt && isEncryptionEnabled()) {
|
||||||
if (encrypt && getFile().isEncrypted()) {
|
dbFile.setContent(_db.toJson(), _creds);
|
||||||
dbFile.setContent(_db.serialize(), _key);
|
|
||||||
} else {
|
} else {
|
||||||
dbFile.setContent(_db.serialize());
|
dbFile.setContent(_db.toJson());
|
||||||
}
|
}
|
||||||
|
|
||||||
File file;
|
String dirName = !BuildConfig.DEBUG ? "Aegis" : "AegisDebug";
|
||||||
FileOutputStream stream = null;
|
File dir = new File(Environment.getExternalStorageDirectory(), dirName);
|
||||||
try {
|
if (!dir.exists() && !dir.mkdirs()) {
|
||||||
String dirName = !BuildConfig.DEBUG ? "Aegis" : "AegisDebug";
|
throw new IOException("error creating external storage directory");
|
||||||
File dir = new File(Environment.getExternalStorageDirectory(), dirName);
|
}
|
||||||
if (!dir.exists() && !dir.mkdirs()) {
|
|
||||||
throw new IOException("error creating external storage directory");
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] bytes = dbFile.serialize();
|
byte[] bytes = dbFile.toBytes();
|
||||||
file = new File(dir.getAbsolutePath(), encrypt ? FILENAME_EXPORT : FILENAME_EXPORT_PLAIN);
|
File file = new File(dir.getAbsolutePath(), encrypt ? FILENAME_EXPORT : FILENAME_EXPORT_PLAIN);
|
||||||
stream = new FileOutputStream(file);
|
try (FileOutputStream stream = new FileOutputStream(file)) {
|
||||||
stream.write(bytes);
|
stream.write(bytes);
|
||||||
} finally {
|
|
||||||
// always close the file stream
|
|
||||||
if (stream != null) {
|
|
||||||
stream.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return file.getAbsolutePath();
|
return file.getAbsolutePath();
|
||||||
|
@ -193,25 +159,38 @@ public class DatabaseManager {
|
||||||
return _db.getEntryByUUID(uuid);
|
return _db.getEntryByUUID(uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MasterKey getMasterKey() {
|
public DatabaseFileCredentials getCredentials() {
|
||||||
assertState(false, true);
|
assertState(false, true);
|
||||||
return _key;
|
return _creds;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DatabaseFile getFile() {
|
public void setCredentials(DatabaseFileCredentials creds) {
|
||||||
return _file;
|
assertState(false, true);
|
||||||
|
_creds = creds;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void enableEncryption(MasterKey key, SlotList slots) {
|
public DatabaseFile.Header getFileHeader() {
|
||||||
assertState(false, true);
|
assertLoaded(true);
|
||||||
_key = key;
|
return _file.getHeader();
|
||||||
_file.setSlots(slots);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void disableEncryption() {
|
public boolean isEncryptionEnabled() {
|
||||||
|
assertLoaded(true);
|
||||||
|
return _encrypt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void enableEncryption(DatabaseFileCredentials creds) throws DatabaseManagerException {
|
||||||
assertState(false, true);
|
assertState(false, true);
|
||||||
_key = null;
|
_creds = creds;
|
||||||
_file.setSlots(null);
|
_encrypt = true;
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disableEncryption() throws DatabaseManagerException {
|
||||||
|
assertState(false, true);
|
||||||
|
_creds = null;
|
||||||
|
_encrypt = false;
|
||||||
|
save();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isLoaded() {
|
public boolean isLoaded() {
|
||||||
|
@ -223,16 +202,20 @@ public class DatabaseManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertState(boolean locked, boolean loaded) {
|
private void assertState(boolean locked, boolean loaded) {
|
||||||
if (isLoaded() && !loaded) {
|
assertLoaded(loaded);
|
||||||
throw new AssertionError("database file has not been loaded yet");
|
|
||||||
} else if (!isLoaded() && loaded) {
|
|
||||||
throw new AssertionError("database file has is already been loaded");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isLocked() && !locked) {
|
if (isLocked() && !locked) {
|
||||||
throw new AssertionError("database file has not been unlocked yet");
|
throw new AssertionError("database file has not been unlocked yet");
|
||||||
} else if (!isLocked() && locked) {
|
} else if (!isLocked() && locked) {
|
||||||
throw new AssertionError("database file has is already been unlocked");
|
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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,9 +20,9 @@ public class PasswordSlot extends RawSlot {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JSONObject serialize() {
|
public JSONObject toJson() {
|
||||||
try {
|
try {
|
||||||
JSONObject obj = super.serialize();
|
JSONObject obj = super.toJson();
|
||||||
obj.put("n", _n);
|
obj.put("n", _n);
|
||||||
obj.put("r", _r);
|
obj.put("r", _r);
|
||||||
obj.put("p", _p);
|
obj.put("p", _p);
|
||||||
|
|
|
@ -10,7 +10,6 @@ import java.security.InvalidKeyException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import javax.crypto.AEADBadTagException;
|
|
||||||
import javax.crypto.BadPaddingException;
|
import javax.crypto.BadPaddingException;
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.IllegalBlockSizeException;
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
|
@ -85,7 +84,7 @@ public abstract class Slot implements Serializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public JSONObject serialize() {
|
public JSONObject toJson() {
|
||||||
try {
|
try {
|
||||||
JSONObject obj = new JSONObject();
|
JSONObject obj = new JSONObject();
|
||||||
JSONObject paramObj = _encryptedMasterKeyParams.toJson();
|
JSONObject paramObj = _encryptedMasterKeyParams.toJson();
|
||||||
|
@ -114,7 +113,7 @@ public abstract class Slot implements Serializable {
|
||||||
|
|
||||||
JSONObject paramObj = obj.getJSONObject("key_params");
|
JSONObject paramObj = obj.getJSONObject("key_params");
|
||||||
_encryptedMasterKey = Hex.decode(obj.getString("key"));
|
_encryptedMasterKey = Hex.decode(obj.getString("key"));
|
||||||
_encryptedMasterKeyParams = CryptParameters.parseJson(paramObj);
|
_encryptedMasterKeyParams = CryptParameters.fromJson(paramObj);
|
||||||
} catch (JSONException | HexException e) {
|
} catch (JSONException | HexException e) {
|
||||||
throw new SlotException(e);
|
throw new SlotException(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,16 +12,16 @@ import java.util.List;
|
||||||
public class SlotList implements Iterable<Slot>, Serializable {
|
public class SlotList implements Iterable<Slot>, Serializable {
|
||||||
private List<Slot> _slots = new ArrayList<>();
|
private List<Slot> _slots = new ArrayList<>();
|
||||||
|
|
||||||
public static JSONArray serialize(SlotList slots) {
|
public JSONArray toJson() {
|
||||||
JSONArray array = new JSONArray();
|
JSONArray array = new JSONArray();
|
||||||
for (Slot slot : slots) {
|
for (Slot slot : this) {
|
||||||
array.put(slot.serialize());
|
array.put(slot.toJson());
|
||||||
}
|
}
|
||||||
|
|
||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SlotList deserialize(JSONArray array) throws SlotListException {
|
public static SlotList fromJson(JSONArray array) throws SlotListException {
|
||||||
SlotList slots = new SlotList();
|
SlotList slots = new SlotList();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -4,16 +4,16 @@ import org.json.JSONObject;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import me.impy.aegis.crypto.MasterKey;
|
|
||||||
import me.impy.aegis.db.Database;
|
import me.impy.aegis.db.Database;
|
||||||
import me.impy.aegis.db.DatabaseEntry;
|
import me.impy.aegis.db.DatabaseEntry;
|
||||||
import me.impy.aegis.db.DatabaseException;
|
import me.impy.aegis.db.DatabaseException;
|
||||||
import me.impy.aegis.db.DatabaseFile;
|
import me.impy.aegis.db.DatabaseFile;
|
||||||
|
import me.impy.aegis.db.DatabaseFileCredentials;
|
||||||
import me.impy.aegis.db.DatabaseFileException;
|
import me.impy.aegis.db.DatabaseFileException;
|
||||||
import me.impy.aegis.util.ByteInputStream;
|
import me.impy.aegis.util.ByteInputStream;
|
||||||
|
|
||||||
public class AegisImporter extends DatabaseImporter {
|
public class AegisImporter extends DatabaseImporter {
|
||||||
private MasterKey _key;
|
private DatabaseFileCredentials _creds;
|
||||||
private DatabaseFile _file;
|
private DatabaseFile _file;
|
||||||
|
|
||||||
public AegisImporter(ByteInputStream stream) {
|
public AegisImporter(ByteInputStream stream) {
|
||||||
|
@ -24,8 +24,7 @@ public class AegisImporter extends DatabaseImporter {
|
||||||
public void parse() throws DatabaseImporterException {
|
public void parse() throws DatabaseImporterException {
|
||||||
try {
|
try {
|
||||||
byte[] bytes = _stream.getBytes();
|
byte[] bytes = _stream.getBytes();
|
||||||
_file = new DatabaseFile();
|
_file = DatabaseFile.fromBytes(bytes);
|
||||||
_file.deserialize(bytes);
|
|
||||||
} catch (DatabaseFileException e) {
|
} catch (DatabaseFileException e) {
|
||||||
throw new DatabaseImporterException(e);
|
throw new DatabaseImporterException(e);
|
||||||
}
|
}
|
||||||
|
@ -35,14 +34,13 @@ public class AegisImporter extends DatabaseImporter {
|
||||||
public List<DatabaseEntry> convert() throws DatabaseImporterException {
|
public List<DatabaseEntry> convert() throws DatabaseImporterException {
|
||||||
try {
|
try {
|
||||||
JSONObject obj;
|
JSONObject obj;
|
||||||
if (_file.isEncrypted() && _key != null) {
|
if (_file.isEncrypted() && _creds != null) {
|
||||||
obj = _file.getContent(_key);
|
obj = _file.getContent(_creds);
|
||||||
} else {
|
} else {
|
||||||
obj = _file.getContent();
|
obj = _file.getContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
Database db = new Database();
|
Database db = Database.fromJson(obj);
|
||||||
db.deserialize(obj);
|
|
||||||
return db.getEntries();
|
return db.getEntries();
|
||||||
} catch (DatabaseException | DatabaseFileException e) {
|
} catch (DatabaseException | DatabaseFileException e) {
|
||||||
throw new DatabaseImporterException(e);
|
throw new DatabaseImporterException(e);
|
||||||
|
@ -54,8 +52,8 @@ public class AegisImporter extends DatabaseImporter {
|
||||||
return _file.isEncrypted();
|
return _file.isEncrypted();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setKey(MasterKey key) {
|
public void setCredentials(DatabaseFileCredentials creds) {
|
||||||
_key = key;
|
_creds = creds;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DatabaseFile getFile() {
|
public DatabaseFile getFile() {
|
||||||
|
|
|
@ -42,14 +42,6 @@ public abstract class DatabaseImporter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<DatabaseImporter> create(ByteInputStream stream) {
|
|
||||||
List<DatabaseImporter> list = new ArrayList<>();
|
|
||||||
for (Class<? extends DatabaseImporter> type : _importers.values()) {
|
|
||||||
list.add(create(stream, type));
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Map<String, Class<? extends DatabaseImporter>> getImporters() {
|
public static Map<String, Class<? extends DatabaseImporter>> getImporters() {
|
||||||
return _importers;
|
return _importers;
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,7 +88,7 @@ public abstract class OtpInfo implements Serializable {
|
||||||
_digits = digits;
|
_digits = digits;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static OtpInfo parseJson(String type, JSONObject obj) throws OtpInfoException {
|
public static OtpInfo fromJson(String type, JSONObject obj) throws OtpInfoException {
|
||||||
OtpInfo info;
|
OtpInfo info;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -25,6 +25,7 @@ import me.impy.aegis.R;
|
||||||
import me.impy.aegis.crypto.KeyStoreHandle;
|
import me.impy.aegis.crypto.KeyStoreHandle;
|
||||||
import me.impy.aegis.crypto.KeyStoreHandleException;
|
import me.impy.aegis.crypto.KeyStoreHandleException;
|
||||||
import me.impy.aegis.crypto.MasterKey;
|
import me.impy.aegis.crypto.MasterKey;
|
||||||
|
import me.impy.aegis.db.DatabaseFileCredentials;
|
||||||
import me.impy.aegis.db.slots.FingerprintSlot;
|
import me.impy.aegis.db.slots.FingerprintSlot;
|
||||||
import me.impy.aegis.db.slots.PasswordSlot;
|
import me.impy.aegis.db.slots.PasswordSlot;
|
||||||
import me.impy.aegis.db.slots.Slot;
|
import me.impy.aegis.db.slots.Slot;
|
||||||
|
@ -128,7 +129,7 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
|
||||||
private void setKey(MasterKey key) {
|
private void setKey(MasterKey key) {
|
||||||
// send the master key back to the main activity
|
// send the master key back to the main activity
|
||||||
Intent result = new Intent();
|
Intent result = new Intent();
|
||||||
result.putExtra("key", key);
|
result.putExtra("creds", new DatabaseFileCredentials(key, _slots));
|
||||||
setResult(RESULT_OK, result);
|
setResult(RESULT_OK, result);
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ import javax.crypto.SecretKey;
|
||||||
|
|
||||||
import me.impy.aegis.Preferences;
|
import me.impy.aegis.Preferences;
|
||||||
import me.impy.aegis.R;
|
import me.impy.aegis.R;
|
||||||
import me.impy.aegis.crypto.MasterKey;
|
import me.impy.aegis.db.DatabaseFileCredentials;
|
||||||
import me.impy.aegis.db.DatabaseFileException;
|
import me.impy.aegis.db.DatabaseFileException;
|
||||||
import me.impy.aegis.db.DatabaseManagerException;
|
import me.impy.aegis.db.DatabaseManagerException;
|
||||||
import me.impy.aegis.db.slots.FingerprintSlot;
|
import me.impy.aegis.db.slots.FingerprintSlot;
|
||||||
|
@ -128,9 +128,9 @@ public class IntroActivity extends AppIntro2 implements DerivationTask.Callback
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate the master key
|
// generate the master key
|
||||||
MasterKey masterKey = null;
|
DatabaseFileCredentials creds = null;
|
||||||
if (cryptType != CustomAuthenticationSlide.CRYPT_TYPE_NONE) {
|
if (cryptType != CustomAuthenticationSlide.CRYPT_TYPE_NONE) {
|
||||||
masterKey = MasterKey.generate();
|
creds = new DatabaseFileCredentials();
|
||||||
}
|
}
|
||||||
|
|
||||||
SlotList slots = null;
|
SlotList slots = null;
|
||||||
|
@ -141,10 +141,8 @@ public class IntroActivity extends AppIntro2 implements DerivationTask.Callback
|
||||||
throw new RuntimeException();
|
throw new RuntimeException();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
_passwordSlot.setKey(masterKey, _passwordCipher);
|
_passwordSlot.setKey(creds.getKey(), _passwordCipher);
|
||||||
slots = new SlotList();
|
creds.getSlots().add(_passwordSlot);
|
||||||
slots.add(_passwordSlot);
|
|
||||||
_databaseFile.setSlots(slots);
|
|
||||||
} catch (SlotException e) {
|
} catch (SlotException e) {
|
||||||
setException(e);
|
setException(e);
|
||||||
}
|
}
|
||||||
|
@ -156,8 +154,8 @@ public class IntroActivity extends AppIntro2 implements DerivationTask.Callback
|
||||||
// and add it to the list of slots
|
// and add it to the list of slots
|
||||||
FingerprintSlot slot = _authenticatedSlide.getFingerSlot();
|
FingerprintSlot slot = _authenticatedSlide.getFingerSlot();
|
||||||
Cipher cipher = _authenticatedSlide.getFingerCipher();
|
Cipher cipher = _authenticatedSlide.getFingerCipher();
|
||||||
slot.setKey(masterKey, cipher);
|
slot.setKey(creds.getKey(), cipher);
|
||||||
slots.add(slot);
|
creds.getSlots().add(slot);
|
||||||
} catch (SlotException e) {
|
} catch (SlotException e) {
|
||||||
setException(e);
|
setException(e);
|
||||||
return;
|
return;
|
||||||
|
@ -166,11 +164,11 @@ public class IntroActivity extends AppIntro2 implements DerivationTask.Callback
|
||||||
|
|
||||||
// finally, save the database
|
// finally, save the database
|
||||||
try {
|
try {
|
||||||
JSONObject obj = _database.serialize();
|
JSONObject obj = _database.toJson();
|
||||||
if (cryptType == CustomAuthenticationSlide.CRYPT_TYPE_NONE) {
|
if (cryptType == CustomAuthenticationSlide.CRYPT_TYPE_NONE) {
|
||||||
_databaseFile.setContent(obj);
|
_databaseFile.setContent(obj);
|
||||||
} else {
|
} else {
|
||||||
_databaseFile.setContent(obj, masterKey);
|
_databaseFile.setContent(obj, creds);
|
||||||
}
|
}
|
||||||
DatabaseManager.save(getApplicationContext(), _databaseFile);
|
DatabaseManager.save(getApplicationContext(), _databaseFile);
|
||||||
} catch (DatabaseManagerException | DatabaseFileException e) {
|
} catch (DatabaseManagerException | DatabaseFileException e) {
|
||||||
|
@ -180,7 +178,7 @@ public class IntroActivity extends AppIntro2 implements DerivationTask.Callback
|
||||||
|
|
||||||
// send the master key back to the main activity
|
// send the master key back to the main activity
|
||||||
Intent result = new Intent();
|
Intent result = new Intent();
|
||||||
result.putExtra("key", masterKey);
|
result.putExtra("creds", creds);
|
||||||
setResult(RESULT_OK, result);
|
setResult(RESULT_OK, result);
|
||||||
|
|
||||||
// skip the intro from now on
|
// skip the intro from now on
|
||||||
|
|
|
@ -20,7 +20,7 @@ import java.lang.reflect.UndeclaredThrowableException;
|
||||||
|
|
||||||
import me.impy.aegis.AegisApplication;
|
import me.impy.aegis.AegisApplication;
|
||||||
import me.impy.aegis.R;
|
import me.impy.aegis.R;
|
||||||
import me.impy.aegis.crypto.MasterKey;
|
import me.impy.aegis.db.DatabaseFileCredentials;
|
||||||
import me.impy.aegis.db.DatabaseManagerException;
|
import me.impy.aegis.db.DatabaseManagerException;
|
||||||
import me.impy.aegis.db.DatabaseEntry;
|
import me.impy.aegis.db.DatabaseEntry;
|
||||||
import me.impy.aegis.db.DatabaseManager;
|
import me.impy.aegis.db.DatabaseManager;
|
||||||
|
@ -218,13 +218,13 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
throw new UndeclaredThrowableException(e);
|
throw new UndeclaredThrowableException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
MasterKey key = (MasterKey) data.getSerializableExtra("key");
|
DatabaseFileCredentials creds = (DatabaseFileCredentials) data.getSerializableExtra("creds");
|
||||||
unlockDatabase(key);
|
unlockDatabase(creds);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onDecryptResult(int resultCode, Intent intent) {
|
private void onDecryptResult(int resultCode, Intent intent) {
|
||||||
MasterKey key = (MasterKey) intent.getSerializableExtra("key");
|
DatabaseFileCredentials creds = (DatabaseFileCredentials) intent.getSerializableExtra("creds");
|
||||||
unlockDatabase(key);
|
unlockDatabase(creds);
|
||||||
|
|
||||||
doShortcutActions();
|
doShortcutActions();
|
||||||
}
|
}
|
||||||
|
@ -361,7 +361,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void unlockDatabase(MasterKey key) {
|
private void unlockDatabase(DatabaseFileCredentials creds) {
|
||||||
if (_loaded) {
|
if (_loaded) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -371,15 +371,14 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
_db.load();
|
_db.load();
|
||||||
}
|
}
|
||||||
if (_db.isLocked()) {
|
if (_db.isLocked()) {
|
||||||
if (key == null) {
|
if (creds == null) {
|
||||||
startAuthActivity();
|
startAuthActivity();
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
_db.unlock(key);
|
_db.unlock(creds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (DatabaseManagerException e) {
|
} catch (DatabaseManagerException e) {
|
||||||
e.printStackTrace();
|
|
||||||
Toast.makeText(this, "An error occurred while trying to load/decrypt the database", Toast.LENGTH_LONG).show();
|
Toast.makeText(this, "An error occurred while trying to load/decrypt the database", Toast.LENGTH_LONG).show();
|
||||||
startAuthActivity();
|
startAuthActivity();
|
||||||
return;
|
return;
|
||||||
|
@ -396,7 +395,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
|
|
||||||
private void startAuthActivity() {
|
private void startAuthActivity() {
|
||||||
Intent intent = new Intent(this, AuthActivity.class);
|
Intent intent = new Intent(this, AuthActivity.class);
|
||||||
intent.putExtra("slots", _db.getFile().getSlots());
|
intent.putExtra("slots", _db.getFileHeader().getSlots());
|
||||||
startActivityForResult(intent, CODE_DECRYPT);
|
startActivityForResult(intent, CODE_DECRYPT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -404,7 +403,6 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
try {
|
try {
|
||||||
_db.save();
|
_db.save();
|
||||||
} catch (DatabaseManagerException e) {
|
} catch (DatabaseManagerException e) {
|
||||||
e.printStackTrace();
|
|
||||||
Toast.makeText(this, "An error occurred while trying to save the database", Toast.LENGTH_LONG).show();
|
Toast.makeText(this, "An error occurred while trying to save the database", Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -413,7 +411,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
// hide the lock icon if the database is not unlocked
|
// hide the lock icon if the database is not unlocked
|
||||||
if (_menu != null && !_db.isLocked()) {
|
if (_menu != null && !_db.isLocked()) {
|
||||||
MenuItem item = _menu.findItem(R.id.action_lock);
|
MenuItem item = _menu.findItem(R.id.action_lock);
|
||||||
item.setVisible(_db.getFile().isEncrypted());
|
item.setVisible(_db.isEncryptionEnabled());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,12 +26,12 @@ import javax.crypto.Cipher;
|
||||||
|
|
||||||
import me.impy.aegis.AegisApplication;
|
import me.impy.aegis.AegisApplication;
|
||||||
import me.impy.aegis.R;
|
import me.impy.aegis.R;
|
||||||
import me.impy.aegis.crypto.MasterKey;
|
|
||||||
import me.impy.aegis.db.DatabaseEntry;
|
import me.impy.aegis.db.DatabaseEntry;
|
||||||
|
import me.impy.aegis.db.DatabaseFileCredentials;
|
||||||
|
import me.impy.aegis.db.DatabaseFileException;
|
||||||
import me.impy.aegis.db.DatabaseManager;
|
import me.impy.aegis.db.DatabaseManager;
|
||||||
import me.impy.aegis.db.DatabaseManagerException;
|
import me.impy.aegis.db.DatabaseManagerException;
|
||||||
import me.impy.aegis.db.slots.Slot;
|
import me.impy.aegis.db.slots.Slot;
|
||||||
import me.impy.aegis.db.slots.SlotList;
|
|
||||||
import me.impy.aegis.db.slots.SlotException;
|
import me.impy.aegis.db.slots.SlotException;
|
||||||
import me.impy.aegis.helpers.PermissionHelper;
|
import me.impy.aegis.helpers.PermissionHelper;
|
||||||
import me.impy.aegis.importers.AegisImporter;
|
import me.impy.aegis.importers.AegisImporter;
|
||||||
|
@ -138,7 +138,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
|
||||||
_encryptionPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
_encryptionPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||||
if (!_db.getFile().isEncrypted()) {
|
if (!_db.isEncryptionEnabled()) {
|
||||||
PasswordDialogFragment dialog = new PasswordDialogFragment();
|
PasswordDialogFragment dialog = new PasswordDialogFragment();
|
||||||
// TODO: find a less ugly way to obtain the fragment manager
|
// TODO: find a less ugly way to obtain the fragment manager
|
||||||
dialog.show(getActivity().getSupportFragmentManager(), null);
|
dialog.show(getActivity().getSupportFragmentManager(), null);
|
||||||
|
@ -148,8 +148,11 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
|
||||||
.setMessage("Are you sure you want to disable encryption? This will cause the database to be stored in plain text")
|
.setMessage("Are you sure you want to disable encryption? This will cause the database to be stored in plain text")
|
||||||
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
|
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
_db.disableEncryption();
|
try {
|
||||||
saveDatabase();
|
_db.disableEncryption();
|
||||||
|
} catch (DatabaseManagerException e) {
|
||||||
|
Toast.makeText(getActivity(), "An error occurred while enabling encryption", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
updateEncryptionPreference();
|
updateEncryptionPreference();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -163,10 +166,8 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
|
||||||
_slotsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
_slotsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreferenceClick(Preference preference) {
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
MasterKey masterKey = _db.getMasterKey();
|
|
||||||
Intent intent = new Intent(getActivity(), SlotManagerActivity.class);
|
Intent intent = new Intent(getActivity(), SlotManagerActivity.class);
|
||||||
intent.putExtra("masterKey", masterKey);
|
intent.putExtra("creds", _db.getCredentials());
|
||||||
intent.putExtra("slots", _db.getFile().getSlots());
|
|
||||||
startActivityForResult(intent, CODE_SLOTS);
|
startActivityForResult(intent, CODE_SLOTS);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -249,13 +250,12 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MasterKey key = (MasterKey) data.getSerializableExtra("key");
|
DatabaseFileCredentials creds = (DatabaseFileCredentials) data.getSerializableExtra("creds");
|
||||||
((AegisImporter)_importer).setKey(key);
|
((AegisImporter)_importer).setCredentials(creds);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
importDatabase(_importer);
|
importDatabase(_importer);
|
||||||
} catch (DatabaseImporterException e) {
|
} catch (DatabaseImporterException e) {
|
||||||
e.printStackTrace();
|
|
||||||
Toast.makeText(getActivity(), "An error occurred while trying to parse the file", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getActivity(), "An error occurred while trying to parse the file", Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,25 +269,16 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
|
||||||
}
|
}
|
||||||
|
|
||||||
ByteInputStream stream;
|
ByteInputStream stream;
|
||||||
InputStream fileStream = null;
|
|
||||||
|
|
||||||
try {
|
try (InputStream fileStream = getActivity().getContentResolver().openInputStream(uri)) {
|
||||||
fileStream = getActivity().getContentResolver().openInputStream(uri);
|
|
||||||
stream = ByteInputStream.create(fileStream);
|
stream = ByteInputStream.create(fileStream);
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
Toast.makeText(getActivity(), "Error: File not found", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getActivity(), "Error: File not found", Toast.LENGTH_SHORT).show();
|
||||||
return;
|
return;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
Toast.makeText(getActivity(), "An error occurred while trying to read the file", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getActivity(), "An error occurred while trying to read the file", Toast.LENGTH_SHORT).show();
|
||||||
return;
|
return;
|
||||||
} finally {
|
|
||||||
if (fileStream != null) {
|
|
||||||
try {
|
|
||||||
fileStream.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -299,7 +290,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
|
||||||
_importer = importer;
|
_importer = importer;
|
||||||
|
|
||||||
Intent intent = new Intent(getActivity(), AuthActivity.class);
|
Intent intent = new Intent(getActivity(), AuthActivity.class);
|
||||||
intent.putExtra("slots", ((AegisImporter)_importer).getFile().getSlots());
|
intent.putExtra("slots", ((AegisImporter)_importer).getFile().getHeader().getSlots());
|
||||||
startActivityForResult(intent, CODE_IMPORT_DECRYPT);
|
startActivityForResult(intent, CODE_IMPORT_DECRYPT);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -344,7 +335,6 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
|
||||||
try {
|
try {
|
||||||
filename = _db.export(checked[0]);
|
filename = _db.export(checked[0]);
|
||||||
} catch (DatabaseManagerException e) {
|
} catch (DatabaseManagerException e) {
|
||||||
e.printStackTrace();
|
|
||||||
Toast.makeText(getActivity(), "An error occurred while trying to export the database", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getActivity(), "An error occurred while trying to export the database", Toast.LENGTH_SHORT).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -355,7 +345,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
|
||||||
Toast.makeText(getActivity(), "The database has been exported to: " + filename, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getActivity(), "The database has been exported to: " + filename, Toast.LENGTH_SHORT).show();
|
||||||
})
|
})
|
||||||
.setNegativeButton(android.R.string.cancel, null);
|
.setNegativeButton(android.R.string.cancel, null);
|
||||||
if (_db.getFile().isEncrypted()) {
|
if (_db.isEncryptionEnabled()) {
|
||||||
final String[] items = {"Keep the database encrypted"};
|
final String[] items = {"Keep the database encrypted"};
|
||||||
final boolean[] checkedItems = {true};
|
final boolean[] checkedItems = {true};
|
||||||
builder.setMultiChoiceItems(items, checkedItems, new DialogInterface.OnMultiChoiceClickListener() {
|
builder.setMultiChoiceItems(items, checkedItems, new DialogInterface.OnMultiChoiceClickListener() {
|
||||||
|
@ -375,8 +365,8 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SlotList slots = (SlotList) data.getSerializableExtra("slots");
|
DatabaseFileCredentials creds = (DatabaseFileCredentials) data.getSerializableExtra("creds");
|
||||||
_db.getFile().setSlots(slots);
|
_db.setCredentials(creds);
|
||||||
saveDatabase();
|
saveDatabase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -384,7 +374,6 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
|
||||||
try {
|
try {
|
||||||
_db.save();
|
_db.save();
|
||||||
} catch (DatabaseManagerException e) {
|
} catch (DatabaseManagerException e) {
|
||||||
e.printStackTrace();
|
|
||||||
Toast.makeText(getActivity(), "An error occurred while trying to save the database", Toast.LENGTH_LONG).show();
|
Toast.makeText(getActivity(), "An error occurred while trying to save the database", Toast.LENGTH_LONG).show();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -394,19 +383,17 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSlotResult(Slot slot, Cipher cipher) {
|
public void onSlotResult(Slot slot, Cipher cipher) {
|
||||||
MasterKey masterKey = MasterKey.generate();
|
DatabaseFileCredentials creds = new DatabaseFileCredentials();
|
||||||
|
|
||||||
SlotList slots = new SlotList();
|
|
||||||
try {
|
try {
|
||||||
slot.setKey(masterKey, cipher);
|
slot.setKey(creds.getKey(), cipher);
|
||||||
} catch (SlotException e) {
|
creds.getSlots().add(slot);
|
||||||
|
_db.enableEncryption(creds);
|
||||||
|
} catch (DatabaseManagerException | SlotException e) {
|
||||||
onException(e);
|
onException(e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
slots.add(slot);
|
|
||||||
_db.enableEncryption(masterKey, slots);
|
|
||||||
|
|
||||||
saveDatabase();
|
|
||||||
updateEncryptionPreference();
|
updateEncryptionPreference();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -417,7 +404,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateEncryptionPreference() {
|
private void updateEncryptionPreference() {
|
||||||
boolean encrypted = _db.getFile().isEncrypted();
|
boolean encrypted = _db.isEncryptionEnabled();
|
||||||
_encryptionPreference.setChecked(encrypted, true);
|
_encryptionPreference.setChecked(encrypted, true);
|
||||||
_slotsPreference.setEnabled(encrypted);
|
_slotsPreference.setEnabled(encrypted);
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,10 +110,7 @@ public class ScannerActivity extends AegisActivity implements ZXingScannerView.R
|
||||||
// parse google auth uri
|
// parse google auth uri
|
||||||
Uri uri = Uri.parse(rawResult.getText());
|
Uri uri = Uri.parse(rawResult.getText());
|
||||||
GoogleAuthInfo info = GoogleAuthInfo.parseUri(uri);
|
GoogleAuthInfo info = GoogleAuthInfo.parseUri(uri);
|
||||||
|
DatabaseEntry entry = new DatabaseEntry(info);
|
||||||
DatabaseEntry entry = new DatabaseEntry(info.getOtpInfo());
|
|
||||||
entry.setIssuer(info.getIssuer());
|
|
||||||
entry.setName(info.getAccountName());
|
|
||||||
|
|
||||||
Intent intent = new Intent();
|
Intent intent = new Intent();
|
||||||
intent.putExtra("entry", entry);
|
intent.putExtra("entry", entry);
|
||||||
|
|
|
@ -16,7 +16,7 @@ import javax.crypto.Cipher;
|
||||||
import me.impy.aegis.R;
|
import me.impy.aegis.R;
|
||||||
import me.impy.aegis.crypto.KeyStoreHandle;
|
import me.impy.aegis.crypto.KeyStoreHandle;
|
||||||
import me.impy.aegis.crypto.KeyStoreHandleException;
|
import me.impy.aegis.crypto.KeyStoreHandleException;
|
||||||
import me.impy.aegis.crypto.MasterKey;
|
import me.impy.aegis.db.DatabaseFileCredentials;
|
||||||
import me.impy.aegis.db.slots.FingerprintSlot;
|
import me.impy.aegis.db.slots.FingerprintSlot;
|
||||||
import me.impy.aegis.db.slots.PasswordSlot;
|
import me.impy.aegis.db.slots.PasswordSlot;
|
||||||
import me.impy.aegis.db.slots.Slot;
|
import me.impy.aegis.db.slots.Slot;
|
||||||
|
@ -29,8 +29,7 @@ import me.impy.aegis.ui.views.SlotAdapter;
|
||||||
import me.impy.aegis.ui.dialogs.SlotDialogFragment;
|
import me.impy.aegis.ui.dialogs.SlotDialogFragment;
|
||||||
|
|
||||||
public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Listener, SlotDialogFragment.Listener {
|
public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Listener, SlotDialogFragment.Listener {
|
||||||
private MasterKey _masterKey;
|
private DatabaseFileCredentials _creds;
|
||||||
private SlotList _slots;
|
|
||||||
private SlotAdapter _adapter;
|
private SlotAdapter _adapter;
|
||||||
|
|
||||||
private boolean _edited;
|
private boolean _edited;
|
||||||
|
@ -59,9 +58,8 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
|
||||||
slotsView.setNestedScrollingEnabled(false);
|
slotsView.setNestedScrollingEnabled(false);
|
||||||
|
|
||||||
// load the slots and masterKey
|
// load the slots and masterKey
|
||||||
_masterKey = (MasterKey) getIntent().getSerializableExtra("masterKey");
|
_creds = (DatabaseFileCredentials) getIntent().getSerializableExtra("creds");
|
||||||
_slots = (SlotList) getIntent().getSerializableExtra("slots");
|
for (Slot slot : _creds.getSlots()) {
|
||||||
for (Slot slot : _slots) {
|
|
||||||
_adapter.addSlot(slot);
|
_adapter.addSlot(slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +73,7 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
|
||||||
if (FingerprintHelper.getManager(this) != null) {
|
if (FingerprintHelper.getManager(this) != null) {
|
||||||
try {
|
try {
|
||||||
KeyStoreHandle keyStore = new KeyStoreHandle();
|
KeyStoreHandle keyStore = new KeyStoreHandle();
|
||||||
for (FingerprintSlot slot : _slots.findAll(FingerprintSlot.class)) {
|
for (FingerprintSlot slot : _creds.getSlots().findAll(FingerprintSlot.class)) {
|
||||||
if (keyStore.containsKey(slot.getUUID().toString())) {
|
if (keyStore.containsKey(slot.getUUID().toString())) {
|
||||||
visibility = View.GONE;
|
visibility = View.GONE;
|
||||||
break;
|
break;
|
||||||
|
@ -92,7 +90,7 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
|
||||||
|
|
||||||
private void onSave() {
|
private void onSave() {
|
||||||
Intent intent = new Intent();
|
Intent intent = new Intent();
|
||||||
intent.putExtra("slots", _slots);
|
intent.putExtra("creds", _creds);
|
||||||
setResult(RESULT_OK, intent);
|
setResult(RESULT_OK, intent);
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
@ -150,7 +148,8 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRemoveSlot(Slot slot) {
|
public void onRemoveSlot(Slot slot) {
|
||||||
if (slot instanceof PasswordSlot && _slots.findAll(PasswordSlot.class).size() <= 1) {
|
SlotList slots = _creds.getSlots();
|
||||||
|
if (slot instanceof PasswordSlot && slots.findAll(PasswordSlot.class).size() <= 1) {
|
||||||
Toast.makeText(this, "You must have at least one password slot", Toast.LENGTH_SHORT).show();
|
Toast.makeText(this, "You must have at least one password slot", Toast.LENGTH_SHORT).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -159,7 +158,7 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
|
||||||
.setTitle("Remove slot")
|
.setTitle("Remove slot")
|
||||||
.setMessage("Are you sure you want to remove this slot?")
|
.setMessage("Are you sure you want to remove this slot?")
|
||||||
.setPositiveButton(android.R.string.yes, (dialog, whichButton) -> {
|
.setPositiveButton(android.R.string.yes, (dialog, whichButton) -> {
|
||||||
_slots.remove(slot);
|
slots.remove(slot);
|
||||||
_adapter.removeSlot(slot);
|
_adapter.removeSlot(slot);
|
||||||
_edited = true;
|
_edited = true;
|
||||||
updateFingerprintButton();
|
updateFingerprintButton();
|
||||||
|
@ -171,13 +170,13 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
|
||||||
@Override
|
@Override
|
||||||
public void onSlotResult(Slot slot, Cipher cipher) {
|
public void onSlotResult(Slot slot, Cipher cipher) {
|
||||||
try {
|
try {
|
||||||
slot.setKey(_masterKey, cipher);
|
slot.setKey(_creds.getKey(), cipher);
|
||||||
} catch (SlotException e) {
|
} catch (SlotException e) {
|
||||||
onException(e);
|
onException(e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_slots.add(slot);
|
_creds.getSlots().add(slot);
|
||||||
_adapter.addSlot(slot);
|
_adapter.addSlot(slot);
|
||||||
_edited = true;
|
_edited = true;
|
||||||
updateFingerprintButton();
|
updateFingerprintButton();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue