From 587835ad38072655a8b5564fe100a9913e997622 Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Tue, 13 Feb 2018 22:06:24 +0100 Subject: [PATCH] Move to an entirely JSON encoded file for the database --- .../main/java/me/impy/aegis/db/Database.java | 18 +- .../java/me/impy/aegis/db/DatabaseFile.java | 196 +++++++----------- .../me/impy/aegis/db/DatabaseManager.java | 43 ++-- .../impy/aegis/db/slots/FingerprintSlot.java | 5 + .../me/impy/aegis/db/slots/PasswordSlot.java | 41 ++-- .../java/me/impy/aegis/db/slots/RawSlot.java | 29 --- .../java/me/impy/aegis/db/slots/Slot.java | 27 ++- .../impy/aegis/db/slots/SlotCollection.java | 47 ++--- .../main/java/me/impy/aegis/encoding/Hex.java | 6 +- .../me/impy/aegis/encoding/HexException.java | 7 + .../impy/aegis/importers/AegisImporter.java | 5 +- .../java/me/impy/aegis/ui/IntroActivity.java | 12 +- .../ui/slides/CustomAuthenticatedSlide.java | 10 +- .../me/impy/aegis/util/LittleByteBuffer.java | 38 ---- 14 files changed, 187 insertions(+), 297 deletions(-) create mode 100644 app/src/main/java/me/impy/aegis/encoding/HexException.java delete mode 100644 app/src/main/java/me/impy/aegis/util/LittleByteBuffer.java diff --git a/app/src/main/java/me/impy/aegis/db/Database.java b/app/src/main/java/me/impy/aegis/db/Database.java index abba70f6..a4075610 100644 --- a/app/src/main/java/me/impy/aegis/db/Database.java +++ b/app/src/main/java/me/impy/aegis/db/Database.java @@ -13,11 +13,7 @@ public class Database { private List _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) { diff --git a/app/src/main/java/me/impy/aegis/db/DatabaseFile.java b/app/src/main/java/me/impy/aegis/db/DatabaseFile.java index 18cf1c9e..51e8736b 100644 --- a/app/src/main/java/me/impy/aegis/db/DatabaseFile.java +++ b/app/src/main/java/me/impy/aegis/db/DatabaseFile.java @@ -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; - } } diff --git a/app/src/main/java/me/impy/aegis/db/DatabaseManager.java b/app/src/main/java/me/impy/aegis/db/DatabaseManager.java index 5ff276af..5db8d5d7 100644 --- a/app/src/main/java/me/impy/aegis/db/DatabaseManager.java +++ b/app/src/main/java/me/impy/aegis/db/DatabaseManager.java @@ -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); diff --git a/app/src/main/java/me/impy/aegis/db/slots/FingerprintSlot.java b/app/src/main/java/me/impy/aegis/db/slots/FingerprintSlot.java index 6db19c19..37e8c3ca 100644 --- a/app/src/main/java/me/impy/aegis/db/slots/FingerprintSlot.java +++ b/app/src/main/java/me/impy/aegis/db/slots/FingerprintSlot.java @@ -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; diff --git a/app/src/main/java/me/impy/aegis/db/slots/PasswordSlot.java b/app/src/main/java/me/impy/aegis/db/slots/PasswordSlot.java index ed029fb9..6835292f 100644 --- a/app/src/main/java/me/impy/aegis/db/slots/PasswordSlot.java +++ b/app/src/main/java/me/impy/aegis/db/slots/PasswordSlot.java @@ -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; diff --git a/app/src/main/java/me/impy/aegis/db/slots/RawSlot.java b/app/src/main/java/me/impy/aegis/db/slots/RawSlot.java index ed2db6cc..cbf83d1d 100644 --- a/app/src/main/java/me/impy/aegis/db/slots/RawSlot.java +++ b/app/src/main/java/me/impy/aegis/db/slots/RawSlot.java @@ -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; diff --git a/app/src/main/java/me/impy/aegis/db/slots/Slot.java b/app/src/main/java/me/impy/aegis/db/slots/Slot.java index 92d0aca2..a5d8ec9a 100644 --- a/app/src/main/java/me/impy/aegis/db/slots/Slot.java +++ b/app/src/main/java/me/impy/aegis/db/slots/Slot.java @@ -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; } diff --git a/app/src/main/java/me/impy/aegis/db/slots/SlotCollection.java b/app/src/main/java/me/impy/aegis/db/slots/SlotCollection.java index c58f0ebc..e34b2db5 100644 --- a/app/src/main/java/me/impy/aegis/db/slots/SlotCollection.java +++ b/app/src/main/java/me/impy/aegis/db/slots/SlotCollection.java @@ -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, Serializable { private List _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, 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); } diff --git a/app/src/main/java/me/impy/aegis/encoding/Hex.java b/app/src/main/java/me/impy/aegis/encoding/Hex.java index 86d12d88..604dd944 100644 --- a/app/src/main/java/me/impy/aegis/encoding/Hex.java +++ b/app/src/main/java/me/impy/aegis/encoding/Hex.java @@ -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); } diff --git a/app/src/main/java/me/impy/aegis/encoding/HexException.java b/app/src/main/java/me/impy/aegis/encoding/HexException.java new file mode 100644 index 00000000..200f8d78 --- /dev/null +++ b/app/src/main/java/me/impy/aegis/encoding/HexException.java @@ -0,0 +1,7 @@ +package me.impy.aegis.encoding; + +public class HexException extends Exception { + public HexException(String message) { + super(message); + } +} diff --git a/app/src/main/java/me/impy/aegis/importers/AegisImporter.java b/app/src/main/java/me/impy/aegis/importers/AegisImporter.java index 94ba2051..4f24619c 100644 --- a/app/src/main/java/me/impy/aegis/importers/AegisImporter.java +++ b/app/src/main/java/me/impy/aegis/importers/AegisImporter.java @@ -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 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(); } diff --git a/app/src/main/java/me/impy/aegis/ui/IntroActivity.java b/app/src/main/java/me/impy/aegis/ui/IntroActivity.java index 5710d6e9..6ac33e8d 100644 --- a/app/src/main/java/me/impy/aegis/ui/IntroActivity.java +++ b/app/src/main/java/me/impy/aegis/ui/IntroActivity.java @@ -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) { diff --git a/app/src/main/java/me/impy/aegis/ui/slides/CustomAuthenticatedSlide.java b/app/src/main/java/me/impy/aegis/ui/slides/CustomAuthenticatedSlide.java index cc975104..6146f6b0 100644 --- a/app/src/main/java/me/impy/aegis/ui/slides/CustomAuthenticatedSlide.java +++ b/app/src/main/java/me/impy/aegis/ui/slides/CustomAuthenticatedSlide.java @@ -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); } diff --git a/app/src/main/java/me/impy/aegis/util/LittleByteBuffer.java b/app/src/main/java/me/impy/aegis/util/LittleByteBuffer.java deleted file mode 100644 index 314abecc..00000000 --- a/app/src/main/java/me/impy/aegis/util/LittleByteBuffer.java +++ /dev/null @@ -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)); } -}