diff --git a/app/src/main/java/me/impy/aegis/crypto/CryptParameters.java b/app/src/main/java/me/impy/aegis/crypto/CryptParameters.java index c5971102..0faa4eb4 100644 --- a/app/src/main/java/me/impy/aegis/crypto/CryptParameters.java +++ b/app/src/main/java/me/impy/aegis/crypto/CryptParameters.java @@ -1,6 +1,36 @@ package me.impy.aegis.crypto; -public class CryptParameters { +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.Serializable; + +import me.impy.aegis.encoding.Hex; +import me.impy.aegis.encoding.HexException; + +public class CryptParameters implements Serializable { public byte[] Nonce; public byte[] Tag; + + public JSONObject toJson() { + JSONObject obj = new JSONObject(); + + try { + obj.put("nonce", Hex.encode(Nonce)); + obj.put("tag", Hex.encode(Tag)); + } catch (JSONException e) { + throw new RuntimeException(e); + } + + return obj; + } + + public static CryptParameters parseJson(JSONObject obj) throws JSONException, HexException { + byte[] tag = Hex.decode(obj.getString("tag")); + byte[] nonce = Hex.decode(obj.getString("nonce")); + return new CryptParameters() {{ + Tag = tag; + Nonce = nonce; + }}; + } } diff --git a/app/src/main/java/me/impy/aegis/crypto/CryptoUtils.java b/app/src/main/java/me/impy/aegis/crypto/CryptoUtils.java index 37eb361e..9133b9fa 100644 --- a/app/src/main/java/me/impy/aegis/crypto/CryptoUtils.java +++ b/app/src/main/java/me/impy/aegis/crypto/CryptoUtils.java @@ -2,16 +2,13 @@ package me.impy.aegis.crypto; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.lang.reflect.UndeclaredThrowableException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; -import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; -import java.security.spec.InvalidKeySpecException; import java.util.Arrays; import javax.crypto.BadPaddingException; @@ -20,20 +17,16 @@ import javax.crypto.IllegalBlockSizeException; import javax.crypto.KeyGenerator; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; -import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.spongycastle.crypto.generators.SCrypt; public class CryptoUtils { - public static final String CRYPTO_HASH = "SHA-256"; - - public static final String CRYPTO_CIPHER_RAW = "AES/ECB/NoPadding"; - public static final byte CRYPTO_KEY_SIZE = 32; - - public static final String CRYPTO_CIPHER_AEAD = "AES/GCM/NoPadding"; - public static final byte CRYPTO_TAG_SIZE = 16; - public static final byte CRYPTO_NONCE_SIZE = 12; + public static final String CRYPTO_AEAD = "AES/GCM/NoPadding"; + public static final byte CRYPTO_AEAD_KEY_SIZE = 32; + public static final byte CRYPTO_AEAD_TAG_SIZE = 16; + public static final byte CRYPTO_AEAD_NONCE_SIZE = 12; public static final int CRYPTO_SCRYPT_N = 1 << 15; public static final int CRYPTO_SCRYPT_r = 8; @@ -41,23 +34,36 @@ public class CryptoUtils { public static SecretKey deriveKey(char[] password, byte[] salt, int n, int r, int p) { byte[] bytes = toBytes(password); - byte[] keyBytes = SCrypt.generate(bytes, salt, n, r, p, CRYPTO_KEY_SIZE); + byte[] keyBytes = SCrypt.generate(bytes, salt, n, r, p, CRYPTO_AEAD_KEY_SIZE); return new SecretKeySpec(keyBytes, 0, keyBytes.length, "AES"); } - public static Cipher createCipher(SecretKey key, int opmode) + public static Cipher createEncryptCipher(SecretKey key) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException { - byte[] nonce = generateNonce(); - return createCipher(key, opmode, nonce); + return createCipher(key, Cipher.ENCRYPT_MODE, null); } - public static Cipher createCipher(SecretKey key, int opmode, byte[] nonce) + public static Cipher createDecryptCipher(SecretKey key, byte[] nonce) + throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, + InvalidKeyException, NoSuchPaddingException { + return createCipher(key, Cipher.DECRYPT_MODE, nonce); + } + + private static Cipher createCipher(SecretKey key, int opmode, byte[] nonce) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException { - IvParameterSpec spec = new IvParameterSpec(nonce); - Cipher cipher = Cipher.getInstance(CRYPTO_CIPHER_AEAD); - cipher.init(opmode, key, spec); + Cipher cipher = Cipher.getInstance(CRYPTO_AEAD); + + // generate the nonce if none is given + // we are not allowed to do this ourselves as "setRandomizedEncryptionRequired" is set to true + if (nonce != null) { + GCMParameterSpec spec = new GCMParameterSpec(CRYPTO_AEAD_TAG_SIZE * 8, nonce); + cipher.init(opmode, key, spec); + } else { + cipher.init(opmode, key); + } + return cipher; } @@ -65,8 +71,8 @@ public class CryptoUtils { throws BadPaddingException, IllegalBlockSizeException { // split off the tag to store it separately byte[] result = cipher.doFinal(data); - byte[] tag = Arrays.copyOfRange(result, result.length - CRYPTO_TAG_SIZE, result.length); - byte[] encrypted = Arrays.copyOfRange(result, 0, result.length - CRYPTO_TAG_SIZE); + byte[] tag = Arrays.copyOfRange(result, result.length - CRYPTO_AEAD_TAG_SIZE, result.length); + byte[] encrypted = Arrays.copyOfRange(result, 0, result.length - CRYPTO_AEAD_TAG_SIZE); return new CryptResult() {{ Parameters = new CryptParameters() {{ @@ -93,23 +99,10 @@ public class CryptoUtils { }}; } - public static byte[] hashKey(SecretKey key) { - MessageDigest hash; - try { - hash = MessageDigest.getInstance(CRYPTO_HASH); - } catch (NoSuchAlgorithmException e) { - throw new AssertionError(e); - } - - byte[] bytes = key.getEncoded(); - hash.update(bytes); - return hash.digest(); - } - public static SecretKey generateKey() { try { KeyGenerator generator = KeyGenerator.getInstance("AES"); - generator.init(CRYPTO_KEY_SIZE * 8); + generator.init(CRYPTO_AEAD_KEY_SIZE * 8); return generator.generateKey(); } catch (NoSuchAlgorithmException e) { throw new AssertionError(e); @@ -117,11 +110,7 @@ public class CryptoUtils { } public static byte[] generateSalt() { - return generateRandomBytes(CRYPTO_KEY_SIZE); - } - - public static byte[] generateNonce() { - return generateRandomBytes(CRYPTO_NONCE_SIZE); + return generateRandomBytes(CRYPTO_AEAD_KEY_SIZE); } public static byte[] generateRandomBytes(int length) { diff --git a/app/src/main/java/me/impy/aegis/crypto/KeyStoreHandle.java b/app/src/main/java/me/impy/aegis/crypto/KeyStoreHandle.java index c7f3afe5..94826d06 100644 --- a/app/src/main/java/me/impy/aegis/crypto/KeyStoreHandle.java +++ b/app/src/main/java/me/impy/aegis/crypto/KeyStoreHandle.java @@ -1,6 +1,5 @@ package me.impy.aegis.crypto; -import android.annotation.SuppressLint; import android.os.Build; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyPermanentlyInvalidatedException; @@ -51,11 +50,11 @@ public class KeyStoreHandle { KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, STORE_NAME); generator.init(new KeyGenParameterSpec.Builder(id, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) - .setBlockModes(KeyProperties.BLOCK_MODE_ECB) + .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .setUserAuthenticationRequired(true) - .setRandomizedEncryptionRequired(false) - .setKeySize(CryptoUtils.CRYPTO_KEY_SIZE * 8) + .setRandomizedEncryptionRequired(true) + .setKeySize(CryptoUtils.CRYPTO_AEAD_KEY_SIZE * 8) .build()); return generator.generateKey(); @@ -81,8 +80,7 @@ public class KeyStoreHandle { // and see if KeyPermanentlyInvalidatedException is thrown if (isSupported()) { try { - @SuppressLint("GetInstance") - Cipher cipher = Cipher.getInstance(CryptoUtils.CRYPTO_CIPHER_RAW); + Cipher cipher = Cipher.getInstance(CryptoUtils.CRYPTO_AEAD); cipher.init(Cipher.ENCRYPT_MODE, key); } catch (KeyPermanentlyInvalidatedException e) { return null; diff --git a/app/src/main/java/me/impy/aegis/crypto/MasterKey.java b/app/src/main/java/me/impy/aegis/crypto/MasterKey.java index c41d1e30..85ba2f73 100644 --- a/app/src/main/java/me/impy/aegis/crypto/MasterKey.java +++ b/app/src/main/java/me/impy/aegis/crypto/MasterKey.java @@ -28,7 +28,7 @@ public class MasterKey implements Serializable { public CryptResult encrypt(byte[] bytes) throws MasterKeyException { try { - Cipher cipher = CryptoUtils.createCipher(_key, Cipher.ENCRYPT_MODE); + Cipher cipher = CryptoUtils.createEncryptCipher(_key); return CryptoUtils.encrypt(bytes, cipher); } catch (NoSuchPaddingException | NoSuchAlgorithmException @@ -42,7 +42,7 @@ public class MasterKey implements Serializable { public CryptResult decrypt(byte[] bytes, CryptParameters params) throws MasterKeyException { try { - Cipher cipher = CryptoUtils.createCipher(_key, Cipher.DECRYPT_MODE, params.Nonce); + Cipher cipher = CryptoUtils.createDecryptCipher(_key, params.Nonce); return CryptoUtils.decrypt(bytes, cipher, params); } catch (NoSuchPaddingException | NoSuchAlgorithmException @@ -55,10 +55,6 @@ public class MasterKey implements Serializable { } } - public byte[] getHash() { - return CryptoUtils.hashKey(_key); - } - public byte[] getBytes() { return _key.getEncoded(); } 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 6e2f0878..ee1e26bc 100644 --- a/app/src/main/java/me/impy/aegis/db/DatabaseFile.java +++ b/app/src/main/java/me/impy/aegis/db/DatabaseFile.java @@ -1,5 +1,6 @@ package me.impy.aegis.db; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -9,11 +10,10 @@ import me.impy.aegis.crypto.CryptParameters; import me.impy.aegis.crypto.CryptResult; import me.impy.aegis.crypto.MasterKey; import me.impy.aegis.crypto.MasterKeyException; -import me.impy.aegis.db.slots.SlotCollection; -import me.impy.aegis.db.slots.SlotCollectionException; +import me.impy.aegis.db.slots.SlotList; +import me.impy.aegis.db.slots.SlotListException; import me.impy.aegis.encoding.Base64; import me.impy.aegis.encoding.Base64Exception; -import me.impy.aegis.encoding.Hex; import me.impy.aegis.encoding.HexException; public class DatabaseFile { @@ -21,22 +21,15 @@ public class DatabaseFile { private Object _content; private CryptParameters _cryptParameters; - private SlotCollection _slots; + private SlotList _slots; public byte[] serialize() { try { - JSONObject cryptObj = null; - if (isEncrypted()) { - cryptObj = new JSONObject(); - cryptObj.put("nonce", Hex.encode(_cryptParameters.Nonce)); - cryptObj.put("tag", Hex.encode(_cryptParameters.Tag)); - } - - // don't write the crypt parameters if the content is not encrypted - boolean plain = _content instanceof JSONObject || _slots == null || cryptObj == null; + // don't write the crypt parameters and slots if the content is not encrypted + boolean plain = _content instanceof JSONObject || !isEncrypted(); JSONObject headerObj = new JSONObject(); - headerObj.put("slots", plain ? JSONObject.NULL : SlotCollection.serialize(_slots)); - headerObj.put("params", plain ? JSONObject.NULL : cryptObj); + headerObj.put("slots", plain ? JSONObject.NULL : SlotList.serialize(_slots)); + headerObj.put("params", plain ? JSONObject.NULL : _cryptParameters.toJson()); JSONObject obj = new JSONObject(); obj.put("version", VERSION); @@ -58,17 +51,14 @@ public class DatabaseFile { throw new DatabaseFileException("unsupported version"); } - JSONObject slotObj = headerObj.optJSONObject("slots"); + JSONArray slotObj = headerObj.optJSONArray("slots"); if (slotObj != null) { - _slots = SlotCollection.deserialize(slotObj); + _slots = SlotList.deserialize(slotObj); } JSONObject cryptObj = headerObj.optJSONObject("params"); if (cryptObj != null) { - _cryptParameters = new CryptParameters() {{ - Nonce = Hex.decode(cryptObj.getString("nonce")); - Tag = Hex.decode(cryptObj.getString("tag")); - }}; + _cryptParameters = CryptParameters.parseJson(cryptObj); } if (cryptObj == null || slotObj == null) { @@ -76,7 +66,7 @@ public class DatabaseFile { } else { _content = obj.getString("db"); } - } catch (SlotCollectionException | UnsupportedEncodingException | JSONException | HexException e) { + } catch (SlotListException | UnsupportedEncodingException | JSONException | HexException e) { throw new DatabaseFileException(e); } } @@ -118,11 +108,11 @@ public class DatabaseFile { } } - public SlotCollection getSlots() { + public SlotList getSlots() { return _slots; } - public void setSlots(SlotCollection slots) { + public void setSlots(SlotList slots) { _slots = slots; } } 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 87f29255..9f7939c1 100644 --- a/app/src/main/java/me/impy/aegis/db/DatabaseManager.java +++ b/app/src/main/java/me/impy/aegis/db/DatabaseManager.java @@ -14,7 +14,7 @@ import java.util.List; import me.impy.aegis.BuildConfig; import me.impy.aegis.crypto.MasterKey; -import me.impy.aegis.db.slots.SlotCollection; +import me.impy.aegis.db.slots.SlotList; public class DatabaseManager { private static final String FILENAME = "aegis.json"; @@ -196,7 +196,7 @@ public class DatabaseManager { return _file; } - public void enableEncryption(MasterKey key, SlotCollection slots) { + public void enableEncryption(MasterKey key, SlotList slots) { assertState(false, true); _key = key; _file.setSlots(slots); 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 cbb0278d..8bc0c33c 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 @@ -1,15 +1,16 @@ package me.impy.aegis.db.slots; -import android.annotation.SuppressLint; - import org.json.JSONException; import org.json.JSONObject; +import java.io.IOException; import java.io.Serializable; +import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.UUID; +import javax.crypto.AEADBadTagException; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; @@ -17,6 +18,8 @@ import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; +import me.impy.aegis.crypto.CryptParameters; +import me.impy.aegis.crypto.CryptResult; import me.impy.aegis.crypto.CryptoUtils; import me.impy.aegis.crypto.MasterKey; import me.impy.aegis.encoding.Hex; @@ -29,40 +32,55 @@ public abstract class Slot implements Serializable { protected UUID _uuid; protected byte[] _encryptedMasterKey; + protected CryptParameters _encryptedMasterKeyParams; protected Slot() { _uuid = UUID.randomUUID(); } - // getKey decrypts the encrypted master key in this slot with the given key and returns it. - public SecretKey getKey(Cipher cipher) throws SlotException { + // getKey decrypts the encrypted master key in this slot using the given cipher and returns it. + public MasterKey getKey(Cipher cipher) throws SlotException, SlotIntegrityException { try { - byte[] decryptedKeyBytes = cipher.doFinal(_encryptedMasterKey); - return new SecretKeySpec(decryptedKeyBytes, CryptoUtils.CRYPTO_CIPHER_AEAD); - } catch (BadPaddingException | IllegalBlockSizeException e) { + CryptResult res = CryptoUtils.decrypt(_encryptedMasterKey, cipher, _encryptedMasterKeyParams); + SecretKey key = new SecretKeySpec(res.Data, CryptoUtils.CRYPTO_AEAD); + return new MasterKey(key); + } catch (AEADBadTagException e) { + throw new SlotIntegrityException(e); + } catch (IOException | BadPaddingException | IllegalBlockSizeException e) { throw new SlotException(e); } } - // setKey encrypts the given master key with the given key and stores the result in this slot. + // setKey encrypts the given master key using the given cipher and stores the result in this slot. public void setKey(MasterKey masterKey, Cipher cipher) throws SlotException { try { byte[] masterKeyBytes = masterKey.getBytes(); - _encryptedMasterKey = cipher.doFinal(masterKeyBytes); + CryptResult res = CryptoUtils.encrypt(masterKeyBytes, cipher); + _encryptedMasterKey = res.Data; + _encryptedMasterKeyParams = res.Parameters; } catch (BadPaddingException | IllegalBlockSizeException e) { throw new SlotException(e); } } - // suppress the AES ECB warning - // this is perfectly safe because we discard this cipher after passing CryptoUtils.CRYPTO_KEY_SIZE bytes through it - @SuppressLint("getInstance") - public static Cipher createCipher(SecretKey key, int mode) throws SlotException { + public static Cipher createEncryptCipher(SecretKey key) throws SlotException { try { - Cipher cipher = Cipher.getInstance(CryptoUtils.CRYPTO_CIPHER_RAW); - cipher.init(mode, key); - return cipher; - } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) { + return CryptoUtils.createEncryptCipher(key); + } catch (InvalidAlgorithmParameterException + | NoSuchPaddingException + | NoSuchAlgorithmException + | InvalidKeyException e) { + throw new SlotException(e); + } + } + + public Cipher createDecryptCipher(SecretKey key) throws SlotException { + try { + return CryptoUtils.createDecryptCipher(key, _encryptedMasterKeyParams.Nonce); + } catch (InvalidAlgorithmParameterException + | NoSuchAlgorithmException + | InvalidKeyException + | NoSuchPaddingException e) { throw new SlotException(e); } } @@ -70,9 +88,11 @@ public abstract class Slot implements Serializable { public JSONObject serialize() { try { JSONObject obj = new JSONObject(); + JSONObject paramObj = _encryptedMasterKeyParams.toJson(); obj.put("type", getType()); obj.put("uuid", _uuid.toString()); obj.put("key", Hex.encode(_encryptedMasterKey)); + obj.put("key_params", paramObj); return obj; } catch (JSONException e) { throw new RuntimeException(e); @@ -84,13 +104,17 @@ public abstract class Slot implements Serializable { if (obj.getInt("type") != getType()) { throw new SlotException("slot type mismatch"); } + // if there is no uuid, generate a new one if (!obj.has("uuid")) { _uuid = UUID.randomUUID(); } else { _uuid = UUID.fromString(obj.getString("uuid")); } + + JSONObject paramObj = obj.getJSONObject("key_params"); _encryptedMasterKey = Hex.decode(obj.getString("key")); + _encryptedMasterKeyParams = CryptParameters.parseJson(paramObj); } catch (JSONException | HexException e) { throw new SlotException(e); } diff --git a/app/src/main/java/me/impy/aegis/db/slots/SlotCollectionException.java b/app/src/main/java/me/impy/aegis/db/slots/SlotCollectionException.java deleted file mode 100644 index 6ded4dec..00000000 --- a/app/src/main/java/me/impy/aegis/db/slots/SlotCollectionException.java +++ /dev/null @@ -1,11 +0,0 @@ -package me.impy.aegis.db.slots; - -public class SlotCollectionException extends Exception { - public SlotCollectionException(Throwable cause) { - super(cause); - } - - public SlotCollectionException(String message) { - super(message); - } -} diff --git a/app/src/main/java/me/impy/aegis/db/slots/SlotIntegrityException.java b/app/src/main/java/me/impy/aegis/db/slots/SlotIntegrityException.java index 79061a5e..b83d6caf 100644 --- a/app/src/main/java/me/impy/aegis/db/slots/SlotIntegrityException.java +++ b/app/src/main/java/me/impy/aegis/db/slots/SlotIntegrityException.java @@ -1,5 +1,11 @@ package me.impy.aegis.db.slots; public class SlotIntegrityException extends Exception { + public SlotIntegrityException() { + } + + public SlotIntegrityException(Throwable cause) { + super(cause); + } } diff --git a/app/src/main/java/me/impy/aegis/db/slots/SlotCollection.java b/app/src/main/java/me/impy/aegis/db/slots/SlotList.java similarity index 51% rename from app/src/main/java/me/impy/aegis/db/slots/SlotCollection.java rename to app/src/main/java/me/impy/aegis/db/slots/SlotList.java index 787f8666..a8c75733 100644 --- a/app/src/main/java/me/impy/aegis/db/slots/SlotCollection.java +++ b/app/src/main/java/me/impy/aegis/db/slots/SlotList.java @@ -6,47 +6,28 @@ import org.json.JSONObject; import java.io.Serializable; import java.util.ArrayList; -import java.util.Arrays; import java.util.Iterator; import java.util.List; -import javax.crypto.Cipher; -import me.impy.aegis.crypto.MasterKey; -import me.impy.aegis.encoding.Hex; -import me.impy.aegis.encoding.HexException; - -public class SlotCollection implements Iterable, Serializable { +public class SlotList implements Iterable, Serializable { private List _slots = new ArrayList<>(); - private byte[] _masterHash; - public static JSONObject serialize(SlotCollection slots) { - try { - JSONObject obj = new JSONObject(); - obj.put("hash", Hex.encode(slots.getMasterHash())); - - JSONArray entries = new JSONArray(); - for (Slot slot : slots) { - entries.put(slot.serialize()); - } - - obj.put("entries", entries); - return obj; - } catch (JSONException e) { - throw new RuntimeException(e); + public static JSONArray serialize(SlotList slots) { + JSONArray array = new JSONArray(); + for (Slot slot : slots) { + array.put(slot.serialize()); } + + return array; } - public static SlotCollection deserialize(JSONObject obj) throws SlotCollectionException { - SlotCollection slots = new SlotCollection(); + public static SlotList deserialize(JSONArray array) throws SlotListException { + SlotList slots = new SlotList(); try { - byte[] masterHash = Hex.decode(obj.getString("hash")); - slots.setMasterHash(masterHash); - - JSONArray entries = obj.getJSONArray("entries"); - for (int i = 0; i < entries.length(); i++) { + for (int i = 0; i < array.length(); i++) { Slot slot; - JSONObject slotObj = entries.getJSONObject(i); + JSONObject slotObj = array.getJSONObject(i); switch (slotObj.getInt("type")) { case Slot.TYPE_RAW: @@ -65,8 +46,8 @@ public class SlotCollection implements Iterable, Serializable { slot.deserialize(slotObj); slots.add(slot); } - } catch (SlotException | JSONException | HexException e) { - throw new SlotCollectionException(e); + } catch (SlotException | JSONException e) { + throw new SlotListException(e); } return slots; @@ -116,26 +97,4 @@ public class SlotCollection implements Iterable, Serializable { public Iterator iterator() { return _slots.iterator(); } - - public void encrypt(Slot slot, MasterKey key, Cipher cipher) throws SlotException { - slot.setKey(key, cipher); - setMasterHash(key.getHash()); - } - - public MasterKey decrypt(Slot slot, Cipher cipher) throws SlotException, SlotIntegrityException { - byte[] hash = getMasterHash(); - MasterKey key = new MasterKey(slot.getKey(cipher)); - if (!Arrays.equals(hash, key.getHash())) { - throw new SlotIntegrityException(); - } - return key; - } - - private void setMasterHash(byte[] masterHash) { - _masterHash = masterHash; - } - - private byte[] getMasterHash() { - return _masterHash; - } } diff --git a/app/src/main/java/me/impy/aegis/db/slots/SlotListException.java b/app/src/main/java/me/impy/aegis/db/slots/SlotListException.java new file mode 100644 index 00000000..32d51342 --- /dev/null +++ b/app/src/main/java/me/impy/aegis/db/slots/SlotListException.java @@ -0,0 +1,11 @@ +package me.impy.aegis.db.slots; + +public class SlotListException extends Exception { + public SlotListException(Throwable cause) { + super(cause); + } + + public SlotListException(String message) { + super(message); + } +} diff --git a/app/src/main/java/me/impy/aegis/ui/AuthActivity.java b/app/src/main/java/me/impy/aegis/ui/AuthActivity.java index ab3311ff..00237f2d 100644 --- a/app/src/main/java/me/impy/aegis/ui/AuthActivity.java +++ b/app/src/main/java/me/impy/aegis/ui/AuthActivity.java @@ -26,7 +26,7 @@ import me.impy.aegis.crypto.MasterKey; import me.impy.aegis.db.slots.FingerprintSlot; import me.impy.aegis.db.slots.PasswordSlot; import me.impy.aegis.db.slots.Slot; -import me.impy.aegis.db.slots.SlotCollection; +import me.impy.aegis.db.slots.SlotList; import me.impy.aegis.db.slots.SlotException; import me.impy.aegis.helpers.FingerprintHelper; import me.impy.aegis.helpers.FingerprintUiHelper; @@ -36,7 +36,7 @@ import me.impy.aegis.ui.tasks.SlotCollectionTask; public class AuthActivity extends AegisActivity implements FingerprintUiHelper.Callback, SlotCollectionTask.Callback { private EditText _textPassword; - private SlotCollection _slots; + private SlotList _slots; private FingerprintUiHelper _fingerHelper; private Cipher _fingerCipher; @@ -57,7 +57,7 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C } Intent intent = getIntent(); - _slots = (SlotCollection) intent.getSerializableExtra("slots"); + _slots = (SlotList) intent.getSerializableExtra("slots"); // only show the fingerprint controls if the api version is new enough, permission is granted, a scanner is found and a fingerprint slot is found FingerprintManager manager = FingerprintHelper.getManager(this); @@ -75,7 +75,7 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C invalidated = true; continue; } - _fingerCipher = Slot.createCipher(key, Cipher.DECRYPT_MODE); + _fingerCipher = slot.createDecryptCipher(key); _fingerHelper = new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, this); boxFingerprint.setVisibility(View.VISIBLE); invalidated = false; 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 7ebea295..076b6960 100644 --- a/app/src/main/java/me/impy/aegis/ui/IntroActivity.java +++ b/app/src/main/java/me/impy/aegis/ui/IntroActivity.java @@ -23,7 +23,7 @@ import me.impy.aegis.db.DatabaseManagerException; import me.impy.aegis.db.slots.FingerprintSlot; import me.impy.aegis.db.slots.PasswordSlot; import me.impy.aegis.db.slots.Slot; -import me.impy.aegis.db.slots.SlotCollection; +import me.impy.aegis.db.slots.SlotList; import me.impy.aegis.db.Database; import me.impy.aegis.db.DatabaseFile; import me.impy.aegis.db.DatabaseManager; @@ -145,7 +145,7 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback { masterKey = MasterKey.generate(); } - SlotCollection slots = null; + SlotList slots = null; if (cryptType != CustomAuthenticationSlide.CRYPT_TYPE_NONE) { // encrypt the master key with a key derived from the user's password // and add it to the list of slots @@ -153,8 +153,8 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback { throw new RuntimeException(); } try { - slots = new SlotCollection(); - slots.encrypt(_passwordSlot, masterKey, _passwordCipher); + _passwordSlot.setKey(masterKey, _passwordCipher); + slots = new SlotList(); slots.add(_passwordSlot); _databaseFile.setSlots(slots); } catch (SlotException e) { @@ -168,7 +168,7 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback { // and add it to the list of slots FingerprintSlot slot = _authenticatedSlide.getFingerSlot(); Cipher cipher = _authenticatedSlide.getFingerCipher(); - slots.encrypt(slot, masterKey, cipher); + slot.setKey(masterKey, cipher); slots.add(slot); } catch (SlotException e) { setException(e); @@ -204,7 +204,7 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback { public void onTaskFinished(SecretKey key) { if (key != null) { try { - _passwordCipher = Slot.createCipher(key, Cipher.ENCRYPT_MODE); + _passwordCipher = Slot.createEncryptCipher(key); } catch (SlotException e) { setException(e); } diff --git a/app/src/main/java/me/impy/aegis/ui/PreferencesFragment.java b/app/src/main/java/me/impy/aegis/ui/PreferencesFragment.java index a51f4609..f1134d80 100644 --- a/app/src/main/java/me/impy/aegis/ui/PreferencesFragment.java +++ b/app/src/main/java/me/impy/aegis/ui/PreferencesFragment.java @@ -32,7 +32,7 @@ import me.impy.aegis.db.DatabaseEntry; import me.impy.aegis.db.DatabaseManager; import me.impy.aegis.db.DatabaseManagerException; import me.impy.aegis.db.slots.Slot; -import me.impy.aegis.db.slots.SlotCollection; +import me.impy.aegis.db.slots.SlotList; import me.impy.aegis.db.slots.SlotException; import me.impy.aegis.helpers.PermissionHelper; import me.impy.aegis.importers.AegisImporter; @@ -369,7 +369,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas return; } - SlotCollection slots = (SlotCollection) data.getSerializableExtra("slots"); + SlotList slots = (SlotList) data.getSerializableExtra("slots"); _db.getFile().setSlots(slots); saveDatabase(); } @@ -390,9 +390,9 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas public void onSlotResult(Slot slot, Cipher cipher) { MasterKey masterKey = MasterKey.generate(); - SlotCollection slots = new SlotCollection(); + SlotList slots = new SlotList(); try { - slots.encrypt(slot, masterKey, cipher); + slot.setKey(masterKey, cipher); } catch (SlotException e) { onException(e); return; diff --git a/app/src/main/java/me/impy/aegis/ui/SlotManagerActivity.java b/app/src/main/java/me/impy/aegis/ui/SlotManagerActivity.java index fb0f453b..461cac7c 100644 --- a/app/src/main/java/me/impy/aegis/ui/SlotManagerActivity.java +++ b/app/src/main/java/me/impy/aegis/ui/SlotManagerActivity.java @@ -20,7 +20,7 @@ import me.impy.aegis.crypto.MasterKey; import me.impy.aegis.db.slots.FingerprintSlot; import me.impy.aegis.db.slots.PasswordSlot; import me.impy.aegis.db.slots.Slot; -import me.impy.aegis.db.slots.SlotCollection; +import me.impy.aegis.db.slots.SlotList; import me.impy.aegis.db.slots.SlotException; import me.impy.aegis.helpers.FingerprintHelper; import me.impy.aegis.ui.dialogs.Dialogs; @@ -31,7 +31,7 @@ import me.impy.aegis.ui.dialogs.SlotDialogFragment; public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Listener, SlotDialogFragment.Listener { private MasterKey _masterKey; - private SlotCollection _slots; + private SlotList _slots; private SlotAdapter _adapter; private boolean _edited = false; @@ -65,7 +65,7 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li // load the slots and masterKey _masterKey = (MasterKey) getIntent().getSerializableExtra("masterKey"); - _slots = (SlotCollection) getIntent().getSerializableExtra("slots"); + _slots = (SlotList) getIntent().getSerializableExtra("slots"); for (Slot slot : _slots) { _adapter.addSlot(slot); } @@ -176,7 +176,7 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li @Override public void onSlotResult(Slot slot, Cipher cipher) { try { - _slots.encrypt(slot, _masterKey, cipher); + slot.setKey(_masterKey, cipher); } catch (SlotException e) { onException(e); return; diff --git a/app/src/main/java/me/impy/aegis/ui/dialogs/FingerprintDialogFragment.java b/app/src/main/java/me/impy/aegis/ui/dialogs/FingerprintDialogFragment.java index c34a2cf9..225b0a14 100644 --- a/app/src/main/java/me/impy/aegis/ui/dialogs/FingerprintDialogFragment.java +++ b/app/src/main/java/me/impy/aegis/ui/dialogs/FingerprintDialogFragment.java @@ -38,7 +38,7 @@ public class FingerprintDialogFragment extends SlotDialogFragment implements Fin try { _slot = new FingerprintSlot(); SecretKey key = new KeyStoreHandle().generateKey(_slot.getUUID().toString()); - _cipher = Slot.createCipher(key, Cipher.ENCRYPT_MODE); + _cipher = Slot.createEncryptCipher(key); _helper = new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, this); } catch (KeyStoreHandleException | SlotException e) { throw new RuntimeException(e); diff --git a/app/src/main/java/me/impy/aegis/ui/dialogs/PasswordDialogFragment.java b/app/src/main/java/me/impy/aegis/ui/dialogs/PasswordDialogFragment.java index 3f46a1e6..a3dc9f7c 100644 --- a/app/src/main/java/me/impy/aegis/ui/dialogs/PasswordDialogFragment.java +++ b/app/src/main/java/me/impy/aegis/ui/dialogs/PasswordDialogFragment.java @@ -51,7 +51,7 @@ public class PasswordDialogFragment extends SlotDialogFragment { DerivationTask task = new DerivationTask(getActivity(), key -> { Cipher cipher; try { - cipher = Slot.createCipher(key, Cipher.ENCRYPT_MODE); + cipher = Slot.createEncryptCipher(key); } catch (SlotException e) { getListener().onException(e); dialog.cancel(); 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 ef7f32b3..b0f6fe8f 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 @@ -113,7 +113,7 @@ public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiH } try { - _fingerCipher = Slot.createCipher(key, Cipher.ENCRYPT_MODE); + _fingerCipher = Slot.createEncryptCipher(key); } catch (Exception e) { throw new UndeclaredThrowableException(e); } diff --git a/app/src/main/java/me/impy/aegis/ui/tasks/SlotCollectionTask.java b/app/src/main/java/me/impy/aegis/ui/tasks/SlotCollectionTask.java index ba784a59..fa23c469 100644 --- a/app/src/main/java/me/impy/aegis/ui/tasks/SlotCollectionTask.java +++ b/app/src/main/java/me/impy/aegis/ui/tasks/SlotCollectionTask.java @@ -2,8 +2,6 @@ package me.impy.aegis.ui.tasks; import android.content.Context; -import java.lang.reflect.UndeclaredThrowableException; - import javax.crypto.Cipher; import javax.crypto.SecretKey; @@ -11,7 +9,7 @@ import me.impy.aegis.crypto.MasterKey; import me.impy.aegis.db.slots.FingerprintSlot; import me.impy.aegis.db.slots.PasswordSlot; import me.impy.aegis.db.slots.Slot; -import me.impy.aegis.db.slots.SlotCollection; +import me.impy.aegis.db.slots.SlotList; import me.impy.aegis.db.slots.SlotException; import me.impy.aegis.db.slots.SlotIntegrityException; @@ -41,15 +39,17 @@ public class SlotCollectionTask extends ProgressDialogTask extends ProgressDialogTask extends ProgressDialogTask