mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-04-26 00:36:11 +00:00
Move to an entirely JSON encoded file for the database
This commit is contained in:
parent
7eaffc933e
commit
587835ad38
14 changed files with 187 additions and 297 deletions
|
@ -13,11 +13,7 @@ public class Database {
|
||||||
private List<DatabaseEntry> _entries = new ArrayList<>();
|
private List<DatabaseEntry> _entries = new ArrayList<>();
|
||||||
private long _counter = 0;
|
private long _counter = 0;
|
||||||
|
|
||||||
public byte[] serialize() throws Exception {
|
public JSONObject serialize() throws Exception {
|
||||||
return serialize(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] serialize(boolean pretty) throws Exception {
|
|
||||||
JSONArray array = new JSONArray();
|
JSONArray array = new JSONArray();
|
||||||
for (DatabaseEntry e : _entries) {
|
for (DatabaseEntry e : _entries) {
|
||||||
array.put(e.serialize());
|
array.put(e.serialize());
|
||||||
|
@ -26,18 +22,14 @@ public class Database {
|
||||||
JSONObject obj = new JSONObject();
|
JSONObject obj = new JSONObject();
|
||||||
obj.put("version", VERSION);
|
obj.put("version", VERSION);
|
||||||
obj.put("entries", array);
|
obj.put("entries", array);
|
||||||
|
return obj;
|
||||||
String string = pretty ? obj.toString(4) : obj.toString();
|
|
||||||
return string.getBytes("UTF-8");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deserialize(byte[] data) throws Exception {
|
public void deserialize(JSONObject obj) throws Exception {
|
||||||
deserialize(data, true);
|
deserialize(obj, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deserialize(byte[] data, boolean incCount) throws Exception {
|
public void deserialize(JSONObject obj, boolean incCount) throws Exception {
|
||||||
JSONObject obj = new JSONObject(new String(data, "UTF-8"));
|
|
||||||
|
|
||||||
// TODO: support different VERSION deserialization providers
|
// TODO: support different VERSION deserialization providers
|
||||||
int ver = obj.getInt("version");
|
int ver = obj.getInt("version");
|
||||||
if (ver != VERSION) {
|
if (ver != VERSION) {
|
||||||
|
|
|
@ -1,163 +1,116 @@
|
||||||
package me.impy.aegis.db;
|
package me.impy.aegis.db;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import android.util.Base64;
|
||||||
import java.io.DataOutputStream;
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.UndeclaredThrowableException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.util.Arrays;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
import javax.crypto.BadPaddingException;
|
||||||
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
|
import javax.crypto.NoSuchPaddingException;
|
||||||
|
|
||||||
import me.impy.aegis.crypto.CryptParameters;
|
import me.impy.aegis.crypto.CryptParameters;
|
||||||
|
import me.impy.aegis.crypto.CryptResult;
|
||||||
|
import me.impy.aegis.crypto.MasterKey;
|
||||||
import me.impy.aegis.db.slots.SlotCollection;
|
import me.impy.aegis.db.slots.SlotCollection;
|
||||||
import me.impy.aegis.crypto.CryptoUtils;
|
import me.impy.aegis.encoding.Hex;
|
||||||
import me.impy.aegis.util.LittleByteBuffer;
|
|
||||||
|
|
||||||
public class DatabaseFile {
|
public class DatabaseFile {
|
||||||
private static final byte SECTION_ENCRYPTION_PARAMETERS = 0x00;
|
public static final byte VERSION = 1;
|
||||||
private static final byte SECTION_SLOTS = 0x01;
|
|
||||||
private static final byte SECTION_END = (byte) 0xFF;
|
|
||||||
private static final byte VERSION = 1;
|
|
||||||
|
|
||||||
private final byte[] HEADER;
|
private Object _content;
|
||||||
|
|
||||||
private byte[] _content;
|
|
||||||
private CryptParameters _cryptParameters;
|
private CryptParameters _cryptParameters;
|
||||||
private SlotCollection _slots;
|
private SlotCollection _slots;
|
||||||
|
|
||||||
public DatabaseFile() {
|
public DatabaseFile() {
|
||||||
try {
|
|
||||||
HEADER = "AEGIS".getBytes("US_ASCII");
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new UndeclaredThrowableException(e);
|
|
||||||
}
|
|
||||||
_slots = new SlotCollection();
|
_slots = new SlotCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] serialize() throws IOException {
|
public byte[] serialize() throws JSONException, UnsupportedEncodingException {
|
||||||
byte[] content = getContent();
|
JSONObject cryptObj = null;
|
||||||
CryptParameters cryptParams = getCryptParameters();
|
if (_cryptParameters != null) {
|
||||||
|
cryptObj = new JSONObject();
|
||||||
// this is dumb, java doesn't provide an endianness-aware data stream
|
cryptObj.put("nonce", Hex.toString(_cryptParameters.Nonce));
|
||||||
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
|
cryptObj.put("tag", Hex.toString(_cryptParameters.Tag));
|
||||||
DataOutputStream stream = new DataOutputStream(byteStream);
|
|
||||||
stream.write(HEADER);
|
|
||||||
stream.write(VERSION);
|
|
||||||
|
|
||||||
if (cryptParams != null) {
|
|
||||||
LittleByteBuffer paramBuffer = LittleByteBuffer.allocate(CryptoUtils.CRYPTO_NONCE_SIZE + CryptoUtils.CRYPTO_TAG_SIZE);
|
|
||||||
paramBuffer.put(cryptParams.Nonce);
|
|
||||||
paramBuffer.put(cryptParams.Tag);
|
|
||||||
writeSection(stream, SECTION_ENCRYPTION_PARAMETERS, paramBuffer.array());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_slots.isEmpty()) {
|
JSONObject headerObj = new JSONObject();
|
||||||
byte[] bytes = SlotCollection.serialize(_slots);
|
headerObj.put("slots", _slots.isEmpty() ? JSONObject.NULL : SlotCollection.serialize(_slots));
|
||||||
writeSection(stream, SECTION_SLOTS, bytes);
|
headerObj.put("params", cryptObj != null ? cryptObj : JSONObject.NULL);
|
||||||
}
|
|
||||||
|
|
||||||
writeSection(stream, SECTION_END, null);
|
JSONObject obj = new JSONObject();
|
||||||
stream.write(content);
|
obj.put("version", VERSION);
|
||||||
return byteStream.toByteArray();
|
obj.put("header", headerObj);
|
||||||
|
obj.put("db", _content);
|
||||||
|
|
||||||
|
String string = obj.toString(4);
|
||||||
|
return string.getBytes("UTF-8");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deserialize(byte[] data) throws Exception {
|
public void deserialize(byte[] data) throws Exception {
|
||||||
LittleByteBuffer buffer = LittleByteBuffer.wrap(data);
|
JSONObject obj = new JSONObject(new String(data, "UTF-8"));
|
||||||
|
JSONObject headerObj = obj.getJSONObject("header");
|
||||||
byte[] header = new byte[HEADER.length];
|
if (obj.getInt("version") > VERSION) {
|
||||||
buffer.get(header);
|
throw new Exception("unsupported version");
|
||||||
if (!Arrays.equals(header, HEADER)) {
|
|
||||||
throw new Exception("Bad header");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: support different version deserialization providers
|
JSONObject slotObj = headerObj.optJSONObject("slots");
|
||||||
byte version = buffer.get();
|
if (slotObj != null) {
|
||||||
if (version != VERSION) {
|
_slots = SlotCollection.deserialize(slotObj);
|
||||||
throw new Exception("Unsupported version");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CryptParameters cryptParams = null;
|
JSONObject cryptObj = headerObj.optJSONObject("params");
|
||||||
SlotCollection slots = new SlotCollection();
|
if (cryptObj != null) {
|
||||||
|
_cryptParameters = new CryptParameters() {{
|
||||||
for (section s = readSection(buffer); s.ID != SECTION_END; s = readSection(buffer)) {
|
Nonce = Hex.toBytes(cryptObj.getString("nonce"));
|
||||||
LittleByteBuffer sBuff = LittleByteBuffer.wrap(s.Data);
|
Tag = Hex.toBytes(cryptObj.getString("tag"));
|
||||||
switch (s.ID) {
|
|
||||||
case SECTION_ENCRYPTION_PARAMETERS:
|
|
||||||
assertLength(s.Data, CryptoUtils.CRYPTO_NONCE_SIZE + CryptoUtils.CRYPTO_TAG_SIZE);
|
|
||||||
|
|
||||||
byte[] nonce = new byte[CryptoUtils.CRYPTO_NONCE_SIZE];
|
|
||||||
byte[] tag = new byte[CryptoUtils.CRYPTO_TAG_SIZE];
|
|
||||||
sBuff.get(nonce);
|
|
||||||
sBuff.get(tag);
|
|
||||||
|
|
||||||
cryptParams = new CryptParameters() {{
|
|
||||||
Nonce = nonce;
|
|
||||||
Tag = tag;
|
|
||||||
}};
|
}};
|
||||||
break;
|
|
||||||
case SECTION_SLOTS:
|
|
||||||
slots = SlotCollection.deserialize(s.Data);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setCryptParameters(cryptParams);
|
if (cryptObj == null || slotObj == null) {
|
||||||
setSlots(slots);
|
_content = obj.getJSONObject("db");
|
||||||
|
} else {
|
||||||
byte[] content = new byte[buffer.remaining()];
|
_content = obj.getString("db");
|
||||||
buffer.get(content);
|
}
|
||||||
setContent(content);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEncrypted() {
|
public boolean isEncrypted() {
|
||||||
return !_slots.isEmpty() && _cryptParameters != null;
|
return !_slots.isEmpty() && _cryptParameters != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void writeSection(DataOutputStream stream, byte id, byte[] data) throws IOException {
|
public JSONObject getContent() {
|
||||||
stream.write(id);
|
return (JSONObject) _content;
|
||||||
|
|
||||||
LittleByteBuffer buffer = LittleByteBuffer.allocate(/* sizeof uint32_t */ 4);
|
|
||||||
if (data == null) {
|
|
||||||
buffer.putInt(0);
|
|
||||||
} else {
|
|
||||||
buffer.putInt(data.length);
|
|
||||||
}
|
|
||||||
stream.write(buffer.array());
|
|
||||||
|
|
||||||
if (data != null) {
|
|
||||||
stream.write(data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static section readSection(LittleByteBuffer buffer) {
|
public JSONObject getContent(MasterKey key)
|
||||||
section s = new section();
|
throws NoSuchPaddingException, InvalidKeyException,
|
||||||
s.ID = buffer.get();
|
NoSuchAlgorithmException, IllegalBlockSizeException,
|
||||||
|
BadPaddingException, InvalidAlgorithmParameterException, IOException, JSONException {
|
||||||
int len = buffer.getInt();
|
byte[] bytes = Base64.decode((String) _content, Base64.NO_WRAP);
|
||||||
s.Data = new byte[len];
|
CryptResult result = key.decrypt(bytes, _cryptParameters);
|
||||||
buffer.get(s.Data);
|
return new JSONObject(new String(result.Data, "UTF-8"));
|
||||||
|
|
||||||
return s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void assertLength(byte[] bytes, int length) throws Exception {
|
public void setContent(JSONObject dbObj) {
|
||||||
if (bytes.length != length) {
|
_content = dbObj;
|
||||||
throw new Exception("Bad length");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getContent() {
|
public void setContent(JSONObject dbObj, MasterKey key)
|
||||||
return _content;
|
throws JSONException, UnsupportedEncodingException,
|
||||||
}
|
NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException,
|
||||||
|
IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
|
||||||
|
String string = dbObj.toString(4);
|
||||||
|
byte[] dbBytes = string.getBytes("UTF-8");
|
||||||
|
|
||||||
public void setContent(byte[] content) {
|
CryptResult result = key.encrypt(dbBytes);
|
||||||
_content = content;
|
_content = new String(Base64.encode(result.Data, Base64.NO_WRAP), "UTF-8");
|
||||||
}
|
_cryptParameters = result.Parameters;
|
||||||
|
|
||||||
public CryptParameters getCryptParameters() {
|
|
||||||
return _cryptParameters;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCryptParameters(CryptParameters parameters) {
|
|
||||||
_cryptParameters = parameters;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public SlotCollection getSlots() {
|
public SlotCollection getSlots() {
|
||||||
|
@ -167,9 +120,4 @@ public class DatabaseFile {
|
||||||
public void setSlots(SlotCollection slots) {
|
public void setSlots(SlotCollection slots) {
|
||||||
_slots = slots;
|
_slots = slots;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class section {
|
|
||||||
byte ID;
|
|
||||||
byte[] Data;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,9 @@ package me.impy.aegis.db;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.io.DataInputStream;
|
import java.io.DataInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
@ -16,9 +19,9 @@ import me.impy.aegis.crypto.CryptResult;
|
||||||
import me.impy.aegis.crypto.MasterKey;
|
import me.impy.aegis.crypto.MasterKey;
|
||||||
|
|
||||||
public class DatabaseManager {
|
public class DatabaseManager {
|
||||||
private static final String FILENAME = "aegis.db";
|
private static final String FILENAME = "aegis.json";
|
||||||
private static final String FILENAME_EXPORT = "aegis_export.db";
|
private static final String FILENAME_EXPORT = "aegis_export.json";
|
||||||
private static final String FILENAME_EXPORT_PLAIN = "aegis_export.json";
|
private static final String FILENAME_EXPORT_PLAIN = "aegis_export_plain.json";
|
||||||
|
|
||||||
private MasterKey _key;
|
private MasterKey _key;
|
||||||
private DatabaseFile _file;
|
private DatabaseFile _file;
|
||||||
|
@ -58,9 +61,9 @@ public class DatabaseManager {
|
||||||
_file.deserialize(fileBytes);
|
_file.deserialize(fileBytes);
|
||||||
|
|
||||||
if (!_file.isEncrypted()) {
|
if (!_file.isEncrypted()) {
|
||||||
byte[] contentBytes = _file.getContent();
|
JSONObject obj = _file.getContent();
|
||||||
_db = new Database();
|
_db = new Database();
|
||||||
_db.deserialize(contentBytes);
|
_db.deserialize(obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,15 +76,13 @@ public class DatabaseManager {
|
||||||
|
|
||||||
public void unlock(MasterKey key) throws Exception {
|
public void unlock(MasterKey key) throws Exception {
|
||||||
assertState(true, true);
|
assertState(true, true);
|
||||||
byte[] encrypted = _file.getContent();
|
JSONObject obj = _file.getContent(key);
|
||||||
CryptParameters params = _file.getCryptParameters();
|
|
||||||
CryptResult result = key.decrypt(encrypted, params);
|
|
||||||
_db = new Database();
|
_db = new Database();
|
||||||
_db.deserialize(result.Data);
|
_db.deserialize(obj);
|
||||||
_key = key;
|
_key = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void save(Context context, DatabaseFile file) throws IOException {
|
public static void save(Context context, DatabaseFile file) throws IOException, JSONException {
|
||||||
byte[] bytes = file.serialize();
|
byte[] bytes = file.serialize();
|
||||||
|
|
||||||
FileOutputStream stream = null;
|
FileOutputStream stream = null;
|
||||||
|
@ -98,26 +99,21 @@ public class DatabaseManager {
|
||||||
|
|
||||||
public void save() throws Exception {
|
public void save() throws Exception {
|
||||||
assertState(false, true);
|
assertState(false, true);
|
||||||
byte[] dbBytes = _db.serialize();
|
JSONObject obj = _db.serialize();
|
||||||
if (!_file.isEncrypted()) {
|
if (_file.isEncrypted()) {
|
||||||
_file.setContent(dbBytes);
|
_file.setContent(obj, _key);
|
||||||
} else {
|
} else {
|
||||||
CryptResult result = _key.encrypt(dbBytes);
|
_file.setContent(obj);
|
||||||
_file.setContent(result.Data);
|
|
||||||
_file.setCryptParameters(result.Parameters);
|
|
||||||
}
|
}
|
||||||
save(_context, _file);
|
save(_context, _file);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String export(boolean encrypt) throws Exception {
|
public String export(boolean encrypt) throws Exception {
|
||||||
assertState(false, true);
|
assertState(false, true);
|
||||||
byte[] bytes = _db.serialize(!encrypt);
|
if (encrypt && getFile().isEncrypted()) {
|
||||||
encrypt = encrypt && getFile().isEncrypted();
|
_file.setContent(_db.serialize(), _key);
|
||||||
if (encrypt) {
|
} else {
|
||||||
CryptResult result = _key.encrypt(bytes);
|
_file.setContent(_db.serialize());
|
||||||
_file.setContent(result.Data);
|
|
||||||
_file.setCryptParameters(result.Parameters);
|
|
||||||
bytes = _file.serialize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
File file;
|
File file;
|
||||||
|
@ -129,6 +125,7 @@ public class DatabaseManager {
|
||||||
throw new IOException("error creating external storage directory");
|
throw new IOException("error creating external storage directory");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
byte[] bytes = _file.serialize();
|
||||||
file = new File(dir.getAbsolutePath(), encrypt ? FILENAME_EXPORT : FILENAME_EXPORT_PLAIN);
|
file = new File(dir.getAbsolutePath(), encrypt ? FILENAME_EXPORT : FILENAME_EXPORT_PLAIN);
|
||||||
stream = new FileOutputStream(file);
|
stream = new FileOutputStream(file);
|
||||||
stream.write(bytes);
|
stream.write(bytes);
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
package me.impy.aegis.db.slots;
|
package me.impy.aegis.db.slots;
|
||||||
|
|
||||||
public class FingerprintSlot extends RawSlot {
|
public class FingerprintSlot extends RawSlot {
|
||||||
|
|
||||||
|
public FingerprintSlot() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte getType() {
|
public byte getType() {
|
||||||
return TYPE_FINGERPRINT;
|
return TYPE_FINGERPRINT;
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
package me.impy.aegis.db.slots;
|
package me.impy.aegis.db.slots;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.spec.InvalidKeySpecException;
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
|
|
||||||
import me.impy.aegis.crypto.CryptoUtils;
|
import me.impy.aegis.crypto.CryptoUtils;
|
||||||
import me.impy.aegis.util.LittleByteBuffer;
|
import me.impy.aegis.encoding.Hex;
|
||||||
|
|
||||||
public class PasswordSlot extends RawSlot {
|
public class PasswordSlot extends RawSlot {
|
||||||
private int _n;
|
private int _n;
|
||||||
|
@ -19,27 +22,22 @@ public class PasswordSlot extends RawSlot {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] serialize() {
|
public JSONObject serialize() throws JSONException {
|
||||||
byte[] bytes = super.serialize();
|
JSONObject obj = super.serialize();
|
||||||
LittleByteBuffer buffer = LittleByteBuffer.wrap(bytes);
|
obj.put("n", _n);
|
||||||
buffer.position(super.getSize());
|
obj.put("r", _r);
|
||||||
buffer.putInt(_n);
|
obj.put("p", _p);
|
||||||
buffer.putInt(_r);
|
obj.put("salt", Hex.toString(_salt));
|
||||||
buffer.putInt(_p);
|
return obj;
|
||||||
buffer.put(_salt);
|
|
||||||
return buffer.array();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deserialize(byte[] data) throws Exception {
|
public void deserialize(JSONObject obj) throws Exception {
|
||||||
super.deserialize(data);
|
super.deserialize(obj);
|
||||||
LittleByteBuffer buffer = LittleByteBuffer.wrap(data);
|
_n = obj.getInt("n");
|
||||||
buffer.position(super.getSize());
|
_r = obj.getInt("r");
|
||||||
_n = buffer.getInt();
|
_p = obj.getInt("p");
|
||||||
_r = buffer.getInt();
|
_salt = Hex.toBytes(obj.getString("salt"));
|
||||||
_p = buffer.getInt();
|
|
||||||
_salt = new byte[CryptoUtils.CRYPTO_SALT_SIZE];
|
|
||||||
buffer.get(_salt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public SecretKey deriveKey(char[] password, byte[] salt, int n, int r, int p) throws InvalidKeySpecException, NoSuchAlgorithmException {
|
public SecretKey deriveKey(char[] password, byte[] salt, int n, int r, int p) throws InvalidKeySpecException, NoSuchAlgorithmException {
|
||||||
|
@ -55,11 +53,6 @@ public class PasswordSlot extends RawSlot {
|
||||||
return CryptoUtils.deriveKey(password, _salt, _n, _r, _p);
|
return CryptoUtils.deriveKey(password, _salt, _n, _r, _p);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSize() {
|
|
||||||
return super.getSize() + /* _n, _r, _p */ 4 + 4 + 4 + CryptoUtils.CRYPTO_SALT_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte getType() {
|
public byte getType() {
|
||||||
return TYPE_DERIVED;
|
return TYPE_DERIVED;
|
||||||
|
|
|
@ -1,40 +1,11 @@
|
||||||
package me.impy.aegis.db.slots;
|
package me.impy.aegis.db.slots;
|
||||||
|
|
||||||
import me.impy.aegis.crypto.CryptoUtils;
|
|
||||||
import me.impy.aegis.util.LittleByteBuffer;
|
|
||||||
|
|
||||||
public class RawSlot extends Slot {
|
public class RawSlot extends Slot {
|
||||||
|
|
||||||
public RawSlot() {
|
public RawSlot() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] serialize() {
|
|
||||||
LittleByteBuffer buffer = LittleByteBuffer.allocate(getSize());
|
|
||||||
buffer.put(getType());
|
|
||||||
buffer.put(_id);
|
|
||||||
buffer.put(_encryptedMasterKey);
|
|
||||||
return buffer.array();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void deserialize(byte[] data) throws Exception {
|
|
||||||
LittleByteBuffer buffer = LittleByteBuffer.wrap(data);
|
|
||||||
if (buffer.get() != getType()) {
|
|
||||||
throw new Exception("slot type mismatch");
|
|
||||||
}
|
|
||||||
_id = new byte[ID_SIZE];
|
|
||||||
buffer.get(_id);
|
|
||||||
_encryptedMasterKey = new byte[CryptoUtils.CRYPTO_KEY_SIZE];
|
|
||||||
buffer.get(_encryptedMasterKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSize() {
|
|
||||||
return 1 + ID_SIZE + CryptoUtils.CRYPTO_KEY_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte getType() {
|
public byte getType() {
|
||||||
return TYPE_RAW;
|
return TYPE_RAW;
|
||||||
|
|
|
@ -2,6 +2,9 @@ package me.impy.aegis.db.slots;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
@ -51,14 +54,24 @@ public abstract class Slot implements Serializable {
|
||||||
return cipher;
|
return cipher;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public JSONObject serialize() throws JSONException {
|
||||||
|
JSONObject obj = new JSONObject();
|
||||||
|
obj.put("type", getType());
|
||||||
|
obj.put("id", Hex.toString(_id));
|
||||||
|
obj.put("key", Hex.toString(_encryptedMasterKey));
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deserialize(JSONObject obj) throws Exception {
|
||||||
|
if (obj.getInt("type") != getType()) {
|
||||||
|
throw new Exception("slot type mismatch");
|
||||||
|
}
|
||||||
|
_id = Hex.toBytes(obj.getString("id"));
|
||||||
|
_encryptedMasterKey = Hex.toBytes(obj.getString("key"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract byte getType();
|
||||||
public String getID() {
|
public String getID() {
|
||||||
return Hex.toString(_id);
|
return Hex.toString(_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract int getSize();
|
|
||||||
public abstract byte getType();
|
|
||||||
|
|
||||||
// a slot has a binary representation
|
|
||||||
public abstract byte[] serialize();
|
|
||||||
public abstract void deserialize(byte[] data) throws Exception;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
package me.impy.aegis.db.slots;
|
package me.impy.aegis.db.slots;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -9,44 +13,38 @@ import java.util.List;
|
||||||
import javax.crypto.BadPaddingException;
|
import javax.crypto.BadPaddingException;
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.IllegalBlockSizeException;
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
import me.impy.aegis.crypto.CryptoUtils;
|
|
||||||
import me.impy.aegis.crypto.MasterKey;
|
import me.impy.aegis.crypto.MasterKey;
|
||||||
import me.impy.aegis.util.LittleByteBuffer;
|
import me.impy.aegis.encoding.Hex;
|
||||||
|
|
||||||
public class SlotCollection implements Iterable<Slot>, Serializable {
|
public class SlotCollection implements Iterable<Slot>, Serializable {
|
||||||
private List<Slot> _slots = new ArrayList<>();
|
private List<Slot> _slots = new ArrayList<>();
|
||||||
private byte[] _masterHash;
|
private byte[] _masterHash;
|
||||||
|
|
||||||
public static byte[] serialize(SlotCollection slots) {
|
public static JSONObject serialize(SlotCollection slots) throws JSONException {
|
||||||
// yep, no streams at this api level
|
JSONObject obj = new JSONObject();
|
||||||
int size = 0;
|
obj.put("hash", Hex.toString(slots.getMasterHash()));
|
||||||
|
|
||||||
|
JSONArray entries = new JSONArray();
|
||||||
for (Slot slot : slots) {
|
for (Slot slot : slots) {
|
||||||
size += slot.getSize();
|
entries.put(slot.serialize());
|
||||||
}
|
|
||||||
size += CryptoUtils.CRYPTO_HASH_SIZE;
|
|
||||||
|
|
||||||
LittleByteBuffer buffer = LittleByteBuffer.allocate(size);
|
|
||||||
buffer.put(slots.getMasterHash());
|
|
||||||
|
|
||||||
for (Slot slot : slots) {
|
|
||||||
byte[] bytes = slot.serialize();
|
|
||||||
buffer.put(bytes);
|
|
||||||
}
|
|
||||||
return buffer.array();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SlotCollection deserialize(byte[] data) throws Exception {
|
obj.put("entries", entries);
|
||||||
LittleByteBuffer buffer = LittleByteBuffer.wrap(data);
|
return obj;
|
||||||
byte[] masterHash = new byte[CryptoUtils.CRYPTO_HASH_SIZE];
|
}
|
||||||
buffer.get(masterHash);
|
|
||||||
|
|
||||||
|
public static SlotCollection deserialize(JSONObject obj) throws Exception {
|
||||||
SlotCollection slots = new SlotCollection();
|
SlotCollection slots = new SlotCollection();
|
||||||
|
|
||||||
|
byte[] masterHash = Hex.toBytes(obj.getString("hash"));
|
||||||
slots.setMasterHash(masterHash);
|
slots.setMasterHash(masterHash);
|
||||||
|
|
||||||
while (buffer.remaining() > 0) {
|
JSONArray entries = obj.getJSONArray("entries");
|
||||||
|
for (int i = 0; i < entries.length(); i++) {
|
||||||
Slot slot;
|
Slot slot;
|
||||||
|
JSONObject slotObj = entries.getJSONObject(i);
|
||||||
|
|
||||||
switch (buffer.peek()) {
|
switch (slotObj.getInt("type")) {
|
||||||
case Slot.TYPE_RAW:
|
case Slot.TYPE_RAW:
|
||||||
slot = new RawSlot();
|
slot = new RawSlot();
|
||||||
break;
|
break;
|
||||||
|
@ -60,10 +58,7 @@ public class SlotCollection implements Iterable<Slot>, Serializable {
|
||||||
throw new Exception("unrecognized slot type");
|
throw new Exception("unrecognized slot type");
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] bytes = new byte[slot.getSize()];
|
slot.deserialize(slotObj);
|
||||||
buffer.get(bytes);
|
|
||||||
|
|
||||||
slot.deserialize(bytes);
|
|
||||||
slots.add(slot);
|
slots.add(slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,11 +15,11 @@ public class Hex {
|
||||||
|
|
||||||
private static final char[] hexCode = "0123456789abcdef".toCharArray();
|
private static final char[] hexCode = "0123456789abcdef".toCharArray();
|
||||||
|
|
||||||
public static byte[] toBytes(String s) {
|
public static byte[] toBytes(String s) throws HexException {
|
||||||
final int len = s.length();
|
final int len = s.length();
|
||||||
|
|
||||||
if (len % 2 != 0)
|
if (len % 2 != 0)
|
||||||
throw new IllegalArgumentException("hexBinary needs to be even-length: " + s);
|
throw new HexException("hexBinary needs to be even-length: " + s);
|
||||||
|
|
||||||
byte[] out = new byte[len / 2];
|
byte[] out = new byte[len / 2];
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ public class Hex {
|
||||||
int h = hexToBin(s.charAt(i));
|
int h = hexToBin(s.charAt(i));
|
||||||
int l = hexToBin(s.charAt(i + 1));
|
int l = hexToBin(s.charAt(i + 1));
|
||||||
if (h == -1 || l == -1)
|
if (h == -1 || l == -1)
|
||||||
throw new IllegalArgumentException("contains illegal character for hexBinary: " + s);
|
throw new HexException("contains illegal character for hexBinary: " + s);
|
||||||
|
|
||||||
out[i / 2] = (byte) (h * 16 + l);
|
out[i / 2] = (byte) (h * 16 + l);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package me.impy.aegis.encoding;
|
||||||
|
|
||||||
|
public class HexException extends Exception {
|
||||||
|
public HexException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import java.util.List;
|
||||||
|
|
||||||
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.DatabaseFile;
|
||||||
import me.impy.aegis.util.ByteInputStream;
|
import me.impy.aegis.util.ByteInputStream;
|
||||||
|
|
||||||
public class AegisImporter extends DatabaseImporter {
|
public class AegisImporter extends DatabaseImporter {
|
||||||
|
@ -15,8 +16,10 @@ public class AegisImporter extends DatabaseImporter {
|
||||||
@Override
|
@Override
|
||||||
public List<DatabaseEntry> convert() throws Exception {
|
public List<DatabaseEntry> convert() throws Exception {
|
||||||
byte[] bytes = _stream.getBytes();
|
byte[] bytes = _stream.getBytes();
|
||||||
|
DatabaseFile file = new DatabaseFile();
|
||||||
|
file.deserialize(bytes);
|
||||||
Database db = new Database();
|
Database db = new Database();
|
||||||
db.deserialize(bytes, false);
|
db.deserialize(file.getContent());
|
||||||
return db.getKeys();
|
return db.getKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,8 @@ import com.github.paolorotolo.appintro.AppIntro;
|
||||||
import com.github.paolorotolo.appintro.AppIntroFragment;
|
import com.github.paolorotolo.appintro.AppIntroFragment;
|
||||||
import com.github.paolorotolo.appintro.model.SliderPage;
|
import com.github.paolorotolo.appintro.model.SliderPage;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
|
|
||||||
|
@ -164,7 +166,7 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback {
|
||||||
try {
|
try {
|
||||||
// encrypt the master key with the fingerprint key
|
// encrypt the master key with the fingerprint key
|
||||||
// and add it to the list of slots
|
// and add it to the list of slots
|
||||||
FingerprintSlot slot = new FingerprintSlot();
|
FingerprintSlot slot = _authenticatedSlide.getFingerSlot();
|
||||||
Cipher cipher = _authenticatedSlide.getFingerCipher();
|
Cipher cipher = _authenticatedSlide.getFingerCipher();
|
||||||
slots.encrypt(slot, masterKey, cipher);
|
slots.encrypt(slot, masterKey, cipher);
|
||||||
slots.add(slot);
|
slots.add(slot);
|
||||||
|
@ -176,13 +178,11 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback {
|
||||||
|
|
||||||
// finally, save the database
|
// finally, save the database
|
||||||
try {
|
try {
|
||||||
byte[] bytes = _database.serialize();
|
JSONObject obj = _database.serialize();
|
||||||
if (cryptType == CustomAuthenticationSlide.CRYPT_TYPE_NONE) {
|
if (cryptType == CustomAuthenticationSlide.CRYPT_TYPE_NONE) {
|
||||||
_databaseFile.setContent(bytes);
|
_databaseFile.setContent(obj);
|
||||||
} else {
|
} else {
|
||||||
CryptResult result = masterKey.encrypt(bytes);
|
_databaseFile.setContent(obj, masterKey);
|
||||||
_databaseFile.setContent(result.Data);
|
|
||||||
_databaseFile.setCryptParameters(result.Parameters);
|
|
||||||
}
|
}
|
||||||
DatabaseManager.save(getApplicationContext(), _databaseFile);
|
DatabaseManager.save(getApplicationContext(), _databaseFile);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
|
@ -41,7 +41,7 @@ public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiH
|
||||||
private TextView _textFingerprint;
|
private TextView _textFingerprint;
|
||||||
private FingerprintUiHelper _fingerHelper;
|
private FingerprintUiHelper _fingerHelper;
|
||||||
private KeyStoreHandle _storeHandle;
|
private KeyStoreHandle _storeHandle;
|
||||||
private FingerprintSlot _slot;
|
private FingerprintSlot _fingerSlot;
|
||||||
private Cipher _fingerCipher;
|
private Cipher _fingerCipher;
|
||||||
private boolean _fingerAuthenticated;
|
private boolean _fingerAuthenticated;
|
||||||
|
|
||||||
|
@ -75,6 +75,10 @@ public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiH
|
||||||
return _fingerCipher;
|
return _fingerCipher;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public FingerprintSlot getFingerSlot() {
|
||||||
|
return _fingerSlot;
|
||||||
|
}
|
||||||
|
|
||||||
public void setBgColor(int color) {
|
public void setBgColor(int color) {
|
||||||
_bgColor = color;
|
_bgColor = color;
|
||||||
}
|
}
|
||||||
|
@ -95,9 +99,9 @@ public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiH
|
||||||
try {
|
try {
|
||||||
if (_storeHandle == null) {
|
if (_storeHandle == null) {
|
||||||
_storeHandle = new KeyStoreHandle();
|
_storeHandle = new KeyStoreHandle();
|
||||||
_slot = new FingerprintSlot();
|
_fingerSlot = new FingerprintSlot();
|
||||||
}
|
}
|
||||||
key = _storeHandle.generateKey(_slot.getID());
|
key = _storeHandle.generateKey(_fingerSlot.getID());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new UndeclaredThrowableException(e);
|
throw new UndeclaredThrowableException(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
package me.impy.aegis.util;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.ByteOrder;
|
|
||||||
|
|
||||||
// LittleByteBuffer wraps a ByteBuffer to extend its API a little.
|
|
||||||
// Its byte order is set to little endian by default.
|
|
||||||
// All this boilerplate just to change the default byte order and add a peek method... Is it worth it? Probably not.
|
|
||||||
public class LittleByteBuffer {
|
|
||||||
private ByteBuffer _buffer;
|
|
||||||
|
|
||||||
private LittleByteBuffer(ByteBuffer buffer) {
|
|
||||||
_buffer = buffer;
|
|
||||||
_buffer.order(ByteOrder.LITTLE_ENDIAN);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte peek() {
|
|
||||||
_buffer.mark();
|
|
||||||
byte b = _buffer.get();
|
|
||||||
_buffer.reset();
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte get() { return _buffer.get(); }
|
|
||||||
public LittleByteBuffer get(byte[] dst) {_buffer.get(dst); return this; }
|
|
||||||
public LittleByteBuffer put(byte b) { _buffer.put(b); return this; }
|
|
||||||
public LittleByteBuffer put(byte[] bytes) { _buffer.put(bytes); return this; }
|
|
||||||
public int remaining() { return _buffer.remaining(); }
|
|
||||||
public byte[] array() { return _buffer.array(); }
|
|
||||||
public LittleByteBuffer putInt(int i) { _buffer.putInt(i); return this; }
|
|
||||||
public LittleByteBuffer putLong(long l) { _buffer.putLong(l); return this; }
|
|
||||||
public int getInt() { return _buffer.getInt(); }
|
|
||||||
public long getLong() { return _buffer.getLong(); }
|
|
||||||
public int position() { return _buffer.position(); }
|
|
||||||
public LittleByteBuffer position(int i) { _buffer.position(i); return this; }
|
|
||||||
public static LittleByteBuffer allocate(int size) { return new LittleByteBuffer(ByteBuffer.allocate(size)); }
|
|
||||||
public static LittleByteBuffer wrap(byte[] bytes) { return new LittleByteBuffer(ByteBuffer.wrap(bytes)); }
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue