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