Make sure we only catch specific exceptions instead of catching them all

This commit is contained in:
Alexander Bakker 2018-03-19 18:00:53 +01:00
parent ebf06aca01
commit f1a03638a0
33 changed files with 608 additions and 425 deletions

View file

@ -27,7 +27,6 @@ import org.spongycastle.crypto.generators.SCrypt;
public class CryptoUtils { public class CryptoUtils {
public static final String CRYPTO_HASH = "SHA-256"; public static final String CRYPTO_HASH = "SHA-256";
public static final byte CRYPTO_HASH_SIZE = 32;
public static final String CRYPTO_CIPHER_RAW = "AES/ECB/NoPadding"; public static final String CRYPTO_CIPHER_RAW = "AES/ECB/NoPadding";
public static final byte CRYPTO_KEY_SIZE = 32; public static final byte CRYPTO_KEY_SIZE = 32;
@ -35,31 +34,36 @@ public class CryptoUtils {
public static final String CRYPTO_CIPHER_AEAD = "AES/GCM/NoPadding"; public static final String CRYPTO_CIPHER_AEAD = "AES/GCM/NoPadding";
public static final byte CRYPTO_TAG_SIZE = 16; public static final byte CRYPTO_TAG_SIZE = 16;
public static final byte CRYPTO_NONCE_SIZE = 12; public static final byte CRYPTO_NONCE_SIZE = 12;
public static final byte CRYPTO_SALT_SIZE = 32;
public static final int CRYPTO_SCRYPT_N = 1 << 15; public static final int CRYPTO_SCRYPT_N = 1 << 15;
public static final int CRYPTO_SCRYPT_r = 8; public static final int CRYPTO_SCRYPT_r = 8;
public static final int CRYPTO_SCRYPT_p = 1; public static final int CRYPTO_SCRYPT_p = 1;
public static SecretKey deriveKey(char[] password, byte[] salt, int n, int r, int p) throws NoSuchAlgorithmException, InvalidKeySpecException { public static SecretKey deriveKey(char[] password, byte[] salt, int n, int r, int p)
throws NoSuchAlgorithmException, InvalidKeySpecException {
byte[] bytes = toBytes(password); 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_KEY_SIZE);
return new SecretKeySpec(keyBytes, 0, keyBytes.length, "AES"); return new SecretKeySpec(keyBytes, 0, keyBytes.length, "AES");
} }
public static Cipher createCipher(SecretKey key, int opmode) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException { public static Cipher createCipher(SecretKey key, int opmode)
throws NoSuchPaddingException, NoSuchAlgorithmException,
InvalidAlgorithmParameterException, InvalidKeyException {
byte[] nonce = generateNonce(); byte[] nonce = generateNonce();
return createCipher(key, opmode, nonce); return createCipher(key, opmode, nonce);
} }
public static Cipher createCipher(SecretKey key, int opmode, byte[] nonce) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException { public static Cipher createCipher(SecretKey key, int opmode, byte[] nonce)
throws NoSuchPaddingException, NoSuchAlgorithmException,
InvalidAlgorithmParameterException, InvalidKeyException {
IvParameterSpec spec = new IvParameterSpec(nonce); IvParameterSpec spec = new IvParameterSpec(nonce);
Cipher cipher = Cipher.getInstance(CRYPTO_CIPHER_AEAD); Cipher cipher = Cipher.getInstance(CRYPTO_CIPHER_AEAD);
cipher.init(opmode, key, spec); cipher.init(opmode, key, spec);
return cipher; return cipher;
} }
public static CryptResult encrypt(byte[] data, Cipher cipher) throws BadPaddingException, IllegalBlockSizeException { public static CryptResult encrypt(byte[] data, Cipher cipher)
throws BadPaddingException, IllegalBlockSizeException {
// split off the tag to store it separately // split off the tag to store it separately
byte[] result = cipher.doFinal(data); byte[] result = cipher.doFinal(data);
byte[] tag = Arrays.copyOfRange(result, result.length - CRYPTO_TAG_SIZE, result.length); byte[] tag = Arrays.copyOfRange(result, result.length - CRYPTO_TAG_SIZE, result.length);
@ -74,7 +78,8 @@ public class CryptoUtils {
}}; }};
} }
public static CryptResult decrypt(byte[] encrypted, Cipher cipher, CryptParameters params) throws IOException, BadPaddingException, IllegalBlockSizeException { public static CryptResult decrypt(byte[] encrypted, Cipher cipher, CryptParameters params)
throws IOException, BadPaddingException, IllegalBlockSizeException {
// append the tag to the ciphertext // append the tag to the ciphertext
ByteArrayOutputStream stream = new ByteArrayOutputStream(); ByteArrayOutputStream stream = new ByteArrayOutputStream();
stream.write(encrypted); stream.write(encrypted);
@ -94,7 +99,7 @@ public class CryptoUtils {
try { try {
hash = MessageDigest.getInstance(CRYPTO_HASH); hash = MessageDigest.getInstance(CRYPTO_HASH);
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
throw new UndeclaredThrowableException(e); throw new AssertionError(e);
} }
byte[] bytes = key.getEncoded(); byte[] bytes = key.getEncoded();
@ -102,10 +107,14 @@ public class CryptoUtils {
return hash.digest(); return hash.digest();
} }
public static SecretKey generateKey() throws NoSuchAlgorithmException { public static SecretKey generateKey() {
try {
KeyGenerator generator = KeyGenerator.getInstance("AES"); KeyGenerator generator = KeyGenerator.getInstance("AES");
generator.init(CRYPTO_KEY_SIZE * 8); generator.init(CRYPTO_KEY_SIZE * 8);
return generator.generateKey(); return generator.generateKey();
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
} }
public static byte[] generateSalt() { public static byte[] generateSalt() {

View file

@ -7,10 +7,12 @@ import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties; import android.security.keystore.KeyProperties;
import java.io.IOException; import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.KeyStoreException; import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableKeyException; import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
@ -40,8 +42,12 @@ public class KeyStoreHandle {
} }
} }
public SecretKey generateKey(String id) throws Exception { public SecretKey generateKey(String id) throws KeyStoreHandleException {
if (isSupported()) { if (!isSupported()) {
throw new KeyStoreHandleException("Symmetric KeyStore keys are not supported in this version of Android");
}
try {
KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, STORE_NAME); KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, STORE_NAME);
generator.init(new KeyGenParameterSpec.Builder(id, generator.init(new KeyGenParameterSpec.Builder(id,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
@ -53,14 +59,23 @@ public class KeyStoreHandle {
.build()); .build());
return generator.generateKey(); return generator.generateKey();
} else { } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException e) {
throw new Exception("Symmetric KeyStore keys are not supported in this version of Android"); throw new KeyStoreHandleException(e);
} }
} }
public SecretKey getKey(String id) public SecretKey getKey(String id) throws KeyStoreHandleException {
throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException { SecretKey key;
SecretKey key = (SecretKey) _keyStore.getKey(id, null);
try {
key = (SecretKey) _keyStore.getKey(id, null);
} catch (UnrecoverableKeyException e) {
return null;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (KeyStoreException e) {
throw new KeyStoreHandleException(e);
}
// try to initialize a dummy cipher // try to initialize a dummy cipher
// and see if KeyPermanentlyInvalidatedException is thrown // and see if KeyPermanentlyInvalidatedException is thrown
@ -71,7 +86,7 @@ public class KeyStoreHandle {
cipher.init(Cipher.ENCRYPT_MODE, key); cipher.init(Cipher.ENCRYPT_MODE, key);
} catch (KeyPermanentlyInvalidatedException e) { } catch (KeyPermanentlyInvalidatedException e) {
return null; return null;
} catch (NoSuchPaddingException | InvalidKeyException e) { } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
@ -79,8 +94,12 @@ public class KeyStoreHandle {
return key; return key;
} }
public void deleteKey(String id) throws KeyStoreException { public void deleteKey(String id) throws KeyStoreHandleException {
try {
_keyStore.deleteEntry(id); _keyStore.deleteEntry(id);
} catch (KeyStoreException e) {
throw new KeyStoreHandleException(e);
}
} }
public static boolean isSupported() { public static boolean isSupported() {

View file

@ -4,4 +4,8 @@ public class KeyStoreHandleException extends Exception {
public KeyStoreHandleException(Throwable cause) { public KeyStoreHandleException(Throwable cause) {
super(cause); super(cause);
} }
public KeyStoreHandleException(String message) {
super(message);
}
} }

View file

@ -22,22 +22,36 @@ public class MasterKey implements Serializable {
_key = key; _key = key;
} }
public static MasterKey generate() throws NoSuchAlgorithmException { public static MasterKey generate() {
return new MasterKey(CryptoUtils.generateKey()); return new MasterKey(CryptoUtils.generateKey());
} }
public CryptResult encrypt(byte[] bytes) public CryptResult encrypt(byte[] bytes) throws MasterKeyException {
throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, InvalidKeyException, try {
NoSuchPaddingException, BadPaddingException, IllegalBlockSizeException {
Cipher cipher = CryptoUtils.createCipher(_key, Cipher.ENCRYPT_MODE); Cipher cipher = CryptoUtils.createCipher(_key, Cipher.ENCRYPT_MODE);
return CryptoUtils.encrypt(bytes, cipher); return CryptoUtils.encrypt(bytes, cipher);
} catch (NoSuchPaddingException
| NoSuchAlgorithmException
| InvalidAlgorithmParameterException
| InvalidKeyException | BadPaddingException
| IllegalBlockSizeException e) {
throw new MasterKeyException(e);
}
} }
public CryptResult decrypt(byte[] bytes, CryptParameters params) public CryptResult decrypt(byte[] bytes, CryptParameters params) throws MasterKeyException {
throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, InvalidKeyException, try {
NoSuchPaddingException, BadPaddingException, IllegalBlockSizeException, IOException {
Cipher cipher = CryptoUtils.createCipher(_key, Cipher.DECRYPT_MODE, params.Nonce); Cipher cipher = CryptoUtils.createCipher(_key, Cipher.DECRYPT_MODE, params.Nonce);
return CryptoUtils.decrypt(bytes, cipher, params); return CryptoUtils.decrypt(bytes, cipher, params);
} catch (NoSuchPaddingException
| NoSuchAlgorithmException
| InvalidAlgorithmParameterException
| InvalidKeyException
| BadPaddingException
| IOException
| IllegalBlockSizeException e) {
throw new MasterKeyException(e);
}
} }
public byte[] getHash() { public byte[] getHash() {

View file

@ -0,0 +1,7 @@
package me.impy.aegis.crypto;
public class MasterKeyException extends Exception {
public MasterKeyException(Throwable cause) {
super(cause);
}
}

View file

@ -9,9 +9,10 @@ public class OTP {
private OTP() { private OTP() {
} }
public static String generateOTP(KeyInfo info) throws InvalidKeyException, NoSuchAlgorithmException { public static String generateOTP(KeyInfo info) throws OTPException {
String otp; String otp;
try {
switch (info.getType()) { switch (info.getType()) {
case "totp": case "totp":
String time = Long.toHexString(System.currentTimeMillis() / 1000 / info.getPeriod()); String time = Long.toHexString(System.currentTimeMillis() / 1000 / info.getPeriod());
@ -21,7 +22,10 @@ public class OTP {
otp = HOTP.generateOTP(info.getSecret(), info.getCounter(), info.getDigits(), false, -1); otp = HOTP.generateOTP(info.getSecret(), info.getCounter(), info.getDigits(), false, -1);
break; break;
default: default:
throw new RuntimeException(); throw new RuntimeException("Bad OTP type");
}
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new OTPException(e);
} }
return otp; return otp;

View file

@ -0,0 +1,7 @@
package me.impy.aegis.crypto.otp;
public class OTPException extends Exception {
public OTPException(Throwable cause) {
super(cause);
}
}

View file

@ -1,6 +1,7 @@
package me.impy.aegis.db; package me.impy.aegis.db;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.util.ArrayList; import java.util.ArrayList;
@ -8,12 +9,15 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import me.impy.aegis.crypto.KeyInfoException;
public class Database { public class Database {
private static final int VERSION = 1; private static final int VERSION = 1;
private List<DatabaseEntry> _entries = new ArrayList<>(); private List<DatabaseEntry> _entries = new ArrayList<>();
public JSONObject serialize() throws Exception { public JSONObject serialize() throws DatabaseException {
try {
JSONArray array = new JSONArray(); JSONArray array = new JSONArray();
for (DatabaseEntry e : _entries) { for (DatabaseEntry e : _entries) {
array.put(e.serialize()); array.put(e.serialize());
@ -23,13 +27,17 @@ public class Database {
obj.put("version", VERSION); obj.put("version", VERSION);
obj.put("entries", array); obj.put("entries", array);
return obj; return obj;
} catch (JSONException e) {
throw new DatabaseException(e);
}
} }
public void deserialize(JSONObject obj) throws Exception { public void deserialize(JSONObject obj) throws DatabaseException {
// TODO: support different VERSION deserialization providers // TODO: support different VERSION deserialization providers
try {
int ver = obj.getInt("version"); int ver = obj.getInt("version");
if (ver != VERSION) { if (ver != VERSION) {
throw new Exception("Unsupported version"); throw new DatabaseException("Unsupported version");
} }
JSONArray array = obj.getJSONArray("entries"); JSONArray array = obj.getJSONArray("entries");
@ -38,6 +46,9 @@ public class Database {
entry.deserialize(array.getJSONObject(i)); entry.deserialize(array.getJSONObject(i));
addKey(entry); addKey(entry);
} }
} catch (JSONException | KeyInfoException e) {
throw new DatabaseException(e);
}
} }
public void addKey(DatabaseEntry entry) { public void addKey(DatabaseEntry entry) {

View file

@ -0,0 +1,11 @@
package me.impy.aegis.db;
public class DatabaseException extends Exception {
public DatabaseException(Throwable cause) {
super(cause);
}
public DatabaseException(String message) {
super(message);
}
}

View file

@ -5,21 +5,16 @@ import android.util.Base64;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.io.IOException;
import java.io.UnsupportedEncodingException; 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.CryptParameters;
import me.impy.aegis.crypto.CryptResult; import me.impy.aegis.crypto.CryptResult;
import me.impy.aegis.crypto.MasterKey; 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.SlotCollection;
import me.impy.aegis.db.slots.SlotCollectionException;
import me.impy.aegis.encoding.Hex; import me.impy.aegis.encoding.Hex;
import me.impy.aegis.encoding.HexException;
public class DatabaseFile { public class DatabaseFile {
public static final byte VERSION = 1; public static final byte VERSION = 1;
@ -32,7 +27,8 @@ public class DatabaseFile {
_slots = new SlotCollection(); _slots = new SlotCollection();
} }
public byte[] serialize() throws JSONException, UnsupportedEncodingException { public byte[] serialize() throws DatabaseFileException {
try {
JSONObject cryptObj = null; JSONObject cryptObj = null;
if (_cryptParameters != null) { if (_cryptParameters != null) {
cryptObj = new JSONObject(); cryptObj = new JSONObject();
@ -53,13 +49,17 @@ public class DatabaseFile {
String string = obj.toString(4); String string = obj.toString(4);
return string.getBytes("UTF-8"); return string.getBytes("UTF-8");
} catch (SlotCollectionException | UnsupportedEncodingException | JSONException e) {
throw new DatabaseFileException(e);
}
} }
public void deserialize(byte[] data) throws Exception { public void deserialize(byte[] data) throws DatabaseFileException {
try {
JSONObject obj = new JSONObject(new String(data, "UTF-8")); JSONObject obj = new JSONObject(new String(data, "UTF-8"));
JSONObject headerObj = obj.getJSONObject("header"); JSONObject headerObj = obj.getJSONObject("header");
if (obj.getInt("version") > VERSION) { if (obj.getInt("version") > VERSION) {
throw new Exception("unsupported version"); throw new DatabaseFileException("unsupported version");
} }
JSONObject slotObj = headerObj.optJSONObject("slots"); JSONObject slotObj = headerObj.optJSONObject("slots");
@ -80,6 +80,9 @@ public class DatabaseFile {
} else { } else {
_content = obj.getString("db"); _content = obj.getString("db");
} }
} catch (SlotCollectionException | UnsupportedEncodingException | JSONException | HexException e) {
throw new DatabaseFileException(e);
}
} }
public boolean isEncrypted() { public boolean isEncrypted() {
@ -90,13 +93,14 @@ public class DatabaseFile {
return (JSONObject) _content; return (JSONObject) _content;
} }
public JSONObject getContent(MasterKey key) public JSONObject getContent(MasterKey key) throws DatabaseFileException {
throws NoSuchPaddingException, InvalidKeyException, try {
NoSuchAlgorithmException, IllegalBlockSizeException,
BadPaddingException, InvalidAlgorithmParameterException, IOException, JSONException {
byte[] bytes = Base64.decode((String) _content, Base64.NO_WRAP); byte[] bytes = Base64.decode((String) _content, Base64.NO_WRAP);
CryptResult result = key.decrypt(bytes, _cryptParameters); CryptResult result = key.decrypt(bytes, _cryptParameters);
return new JSONObject(new String(result.Data, "UTF-8")); return new JSONObject(new String(result.Data, "UTF-8"));
} catch (MasterKeyException | JSONException | UnsupportedEncodingException e) {
throw new DatabaseFileException(e);
}
} }
public void setContent(JSONObject dbObj) { public void setContent(JSONObject dbObj) {
@ -104,16 +108,17 @@ public class DatabaseFile {
_cryptParameters = null; _cryptParameters = null;
} }
public void setContent(JSONObject dbObj, MasterKey key) public void setContent(JSONObject dbObj, MasterKey key) throws DatabaseFileException {
throws JSONException, UnsupportedEncodingException, try {
NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException,
IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
String string = dbObj.toString(4); String string = dbObj.toString(4);
byte[] dbBytes = string.getBytes("UTF-8"); byte[] dbBytes = string.getBytes("UTF-8");
CryptResult result = key.encrypt(dbBytes); CryptResult result = key.encrypt(dbBytes);
_content = new String(Base64.encode(result.Data, Base64.NO_WRAP), "UTF-8"); _content = new String(Base64.encode(result.Data, Base64.NO_WRAP), "UTF-8");
_cryptParameters = result.Parameters; _cryptParameters = result.Parameters;
} catch (MasterKeyException | UnsupportedEncodingException | JSONException e) {
throw new DatabaseFileException(e);
}
} }
public SlotCollection getSlots() { public SlotCollection getSlots() {

View file

@ -0,0 +1,11 @@
package me.impy.aegis.db;
public class DatabaseFileException extends Exception {
public DatabaseFileException(Throwable cause) {
super(cause);
}
public DatabaseFileException(String message) {
super(message);
}
}

View file

@ -3,7 +3,6 @@ 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 org.json.JSONObject;
import java.io.DataInputStream; import java.io.DataInputStream;
@ -35,9 +34,10 @@ public class DatabaseManager {
return file.exists() && file.isFile(); return file.exists() && file.isFile();
} }
public void load() throws Exception { public void load() throws DatabaseManagerException {
assertState(true, false); assertState(true, false);
try {
byte[] fileBytes; byte[] fileBytes;
FileInputStream file = null; FileInputStream file = null;
@ -63,24 +63,33 @@ public class DatabaseManager {
_db = new Database(); _db = new Database();
_db.deserialize(obj); _db.deserialize(obj);
} }
} catch (IOException | DatabaseFileException | DatabaseException e) {
throw new DatabaseManagerException(e);
}
} }
public void lock() throws Exception { public void lock() {
assertState(false, true); assertState(false, true);
// TODO: properly clear everything // TODO: properly clear everything
_key = null; _key = null;
_db = null; _db = null;
} }
public void unlock(MasterKey key) throws Exception { public void unlock(MasterKey key) throws DatabaseManagerException {
assertState(true, true); assertState(true, true);
try {
JSONObject obj = _file.getContent(key); JSONObject obj = _file.getContent(key);
_db = new Database(); _db = new Database();
_db.deserialize(obj); _db.deserialize(obj);
_key = key; _key = key;
} catch (DatabaseFileException | DatabaseException e) {
throw new DatabaseManagerException(e);
}
} }
public static void save(Context context, DatabaseFile file) throws IOException, JSONException { public static void save(Context context, DatabaseFile file) throws DatabaseManagerException {
try {
byte[] bytes = file.serialize(); byte[] bytes = file.serialize();
FileOutputStream stream = null; FileOutputStream stream = null;
@ -93,10 +102,15 @@ public class DatabaseManager {
stream.close(); stream.close();
} }
} }
} catch (IOException | DatabaseFileException e) {
throw new DatabaseManagerException(e);
}
} }
public void save() throws Exception { public void save() throws DatabaseManagerException {
assertState(false, true); assertState(false, true);
try {
JSONObject obj = _db.serialize(); JSONObject obj = _db.serialize();
if (_file.isEncrypted()) { if (_file.isEncrypted()) {
_file.setContent(obj, _key); _file.setContent(obj, _key);
@ -104,11 +118,15 @@ public class DatabaseManager {
_file.setContent(obj); _file.setContent(obj);
} }
save(_context, _file); save(_context, _file);
} catch (DatabaseException | DatabaseFileException e) {
throw new DatabaseManagerException(e);
}
} }
public String export(boolean encrypt) throws Exception { public String export(boolean encrypt) throws DatabaseManagerException {
assertState(false, true); assertState(false, true);
try {
DatabaseFile dbFile = new DatabaseFile(); DatabaseFile dbFile = new DatabaseFile();
dbFile.setSlots(_file.getSlots()); dbFile.setSlots(_file.getSlots());
if (encrypt && getFile().isEncrypted()) { if (encrypt && getFile().isEncrypted()) {
@ -138,34 +156,37 @@ public class DatabaseManager {
} }
return file.getAbsolutePath(); return file.getAbsolutePath();
} catch (DatabaseException | IOException | DatabaseFileException e) {
throw new DatabaseManagerException(e);
}
} }
public void addKey(DatabaseEntry entry) throws Exception { public void addKey(DatabaseEntry entry) {
assertState(false, true); assertState(false, true);
_db.addKey(entry); _db.addKey(entry);
} }
public void removeKey(DatabaseEntry entry) throws Exception { public void removeKey(DatabaseEntry entry) {
assertState(false, true); assertState(false, true);
_db.removeKey(entry); _db.removeKey(entry);
} }
public void replaceKey(DatabaseEntry entry) throws Exception { public void replaceKey(DatabaseEntry entry) {
assertState(false, true); assertState(false, true);
_db.replaceKey(entry); _db.replaceKey(entry);
} }
public void swapKeys(DatabaseEntry entry1, DatabaseEntry entry2) throws Exception { public void swapKeys(DatabaseEntry entry1, DatabaseEntry entry2) {
assertState(false, true); assertState(false, true);
_db.swapKeys(entry1, entry2); _db.swapKeys(entry1, entry2);
} }
public List<DatabaseEntry> getKeys() throws Exception { public List<DatabaseEntry> getKeys() {
assertState(false, true); assertState(false, true);
return _db.getKeys(); return _db.getKeys();
} }
public MasterKey getMasterKey() throws Exception { public MasterKey getMasterKey() {
assertState(false, true); assertState(false, true);
return _key; return _key;
} }
@ -182,17 +203,17 @@ public class DatabaseManager {
return _db == null; return _db == null;
} }
private void assertState(boolean locked, boolean loaded) throws Exception { private void assertState(boolean locked, boolean loaded) {
if (isLoaded() && !loaded) { if (isLoaded() && !loaded) {
throw new Exception("database file has not been loaded yet"); throw new AssertionError("database file has not been loaded yet");
} else if (!isLoaded() && loaded) { } else if (!isLoaded() && loaded) {
throw new Exception("database file has is already been loaded"); throw new AssertionError("database file has is already been loaded");
} }
if (isLocked() && !locked) { if (isLocked() && !locked) {
throw new Exception("database file has not been unlocked yet"); throw new AssertionError("database file has not been unlocked yet");
} else if (!isLocked() && locked) { } else if (!isLocked() && locked) {
throw new Exception("database file has is already been unlocked"); throw new AssertionError("database file has is already been unlocked");
} }
} }
} }

View file

@ -0,0 +1,7 @@
package me.impy.aegis.db;
public class DatabaseManagerException extends Exception {
public DatabaseManagerException(Throwable cause) {
super(cause);
}
}

View file

@ -10,6 +10,7 @@ import javax.crypto.SecretKey;
import me.impy.aegis.crypto.CryptoUtils; import me.impy.aegis.crypto.CryptoUtils;
import me.impy.aegis.encoding.Hex; import me.impy.aegis.encoding.Hex;
import me.impy.aegis.encoding.HexException;
public class PasswordSlot extends RawSlot { public class PasswordSlot extends RawSlot {
private int _n; private int _n;
@ -22,35 +23,51 @@ public class PasswordSlot extends RawSlot {
} }
@Override @Override
public JSONObject serialize() throws JSONException { public JSONObject serialize() throws SlotException {
try {
JSONObject obj = super.serialize(); JSONObject obj = super.serialize();
obj.put("n", _n); obj.put("n", _n);
obj.put("r", _r); obj.put("r", _r);
obj.put("p", _p); obj.put("p", _p);
obj.put("salt", Hex.toString(_salt)); obj.put("salt", Hex.toString(_salt));
return obj; return obj;
} catch (JSONException e) {
throw new SlotException(e);
}
} }
@Override @Override
public void deserialize(JSONObject obj) throws Exception { public void deserialize(JSONObject obj) throws SlotException {
try {
super.deserialize(obj); super.deserialize(obj);
_n = obj.getInt("n"); _n = obj.getInt("n");
_r = obj.getInt("r"); _r = obj.getInt("r");
_p = obj.getInt("p"); _p = obj.getInt("p");
_salt = Hex.toBytes(obj.getString("salt")); _salt = Hex.toBytes(obj.getString("salt"));
} catch (JSONException | HexException e) {
throw new SlotException(e);
}
} }
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 SlotException {
try {
SecretKey key = CryptoUtils.deriveKey(password, salt, n, r, p); SecretKey key = CryptoUtils.deriveKey(password, salt, n, r, p);
_n = n; _n = n;
_r = r; _r = r;
_p = p; _p = p;
_salt = salt; _salt = salt;
return key; return key;
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new SlotException(e);
}
} }
public SecretKey deriveKey(char[] password) throws InvalidKeySpecException, NoSuchAlgorithmException { public SecretKey deriveKey(char[] password) throws SlotException {
try {
return CryptoUtils.deriveKey(password, _salt, _n, _r, _p); return CryptoUtils.deriveKey(password, _salt, _n, _r, _p);
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
throw new SlotException(e);
}
} }
@Override @Override

View file

@ -20,6 +20,7 @@ import javax.crypto.spec.SecretKeySpec;
import me.impy.aegis.crypto.CryptoUtils; import me.impy.aegis.crypto.CryptoUtils;
import me.impy.aegis.crypto.MasterKey; import me.impy.aegis.crypto.MasterKey;
import me.impy.aegis.encoding.Hex; import me.impy.aegis.encoding.Hex;
import me.impy.aegis.encoding.HexException;
public abstract class Slot implements Serializable { public abstract class Slot implements Serializable {
public final static byte TYPE_RAW = 0x00; public final static byte TYPE_RAW = 0x00;
@ -34,37 +35,54 @@ public abstract class Slot implements Serializable {
} }
// getKey decrypts the encrypted master key in this slot with the given key and returns it. // getKey decrypts the encrypted master key in this slot with the given key and returns it.
public SecretKey getKey(Cipher cipher) throws BadPaddingException, IllegalBlockSizeException { public SecretKey getKey(Cipher cipher) throws SlotException {
try {
byte[] decryptedKeyBytes = cipher.doFinal(_encryptedMasterKey); byte[] decryptedKeyBytes = cipher.doFinal(_encryptedMasterKey);
return new SecretKeySpec(decryptedKeyBytes, CryptoUtils.CRYPTO_CIPHER_AEAD); return new SecretKeySpec(decryptedKeyBytes, CryptoUtils.CRYPTO_CIPHER_AEAD);
} catch (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 with the given key and stores the result in this slot.
public void setKey(MasterKey masterKey, Cipher cipher) throws BadPaddingException, IllegalBlockSizeException { public void setKey(MasterKey masterKey, Cipher cipher) throws SlotException {
try {
byte[] masterKeyBytes = masterKey.getBytes(); byte[] masterKeyBytes = masterKey.getBytes();
_encryptedMasterKey = cipher.doFinal(masterKeyBytes); _encryptedMasterKey = cipher.doFinal(masterKeyBytes);
} catch (BadPaddingException | IllegalBlockSizeException e) {
throw new SlotException(e);
}
} }
// suppress the AES ECB warning // suppress the AES ECB warning
// this is perfectly safe because we discard this cipher after passing CryptoUtils.CRYPTO_KEY_SIZE bytes through it // this is perfectly safe because we discard this cipher after passing CryptoUtils.CRYPTO_KEY_SIZE bytes through it
@SuppressLint("getInstance") @SuppressLint("getInstance")
public static Cipher createCipher(SecretKey key, int mode) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException { public static Cipher createCipher(SecretKey key, int mode) throws SlotException {
try {
Cipher cipher = Cipher.getInstance(CryptoUtils.CRYPTO_CIPHER_RAW); Cipher cipher = Cipher.getInstance(CryptoUtils.CRYPTO_CIPHER_RAW);
cipher.init(mode, key); cipher.init(mode, key);
return cipher; return cipher;
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) {
throw new SlotException(e);
}
} }
public JSONObject serialize() throws JSONException { public JSONObject serialize() throws SlotException {
try {
JSONObject obj = new JSONObject(); JSONObject obj = new JSONObject();
obj.put("type", getType()); obj.put("type", getType());
obj.put("uuid", _uuid.toString()); obj.put("uuid", _uuid.toString());
obj.put("key", Hex.toString(_encryptedMasterKey)); obj.put("key", Hex.toString(_encryptedMasterKey));
return obj; return obj;
} catch (JSONException e) {
throw new SlotException(e);
}
} }
public void deserialize(JSONObject obj) throws Exception { public void deserialize(JSONObject obj) throws SlotException {
try {
if (obj.getInt("type") != getType()) { if (obj.getInt("type") != getType()) {
throw new Exception("slot type mismatch"); throw new SlotException("slot type mismatch");
} }
// if there is no uuid, generate a new one // if there is no uuid, generate a new one
if (!obj.has("uuid")) { if (!obj.has("uuid")) {
@ -73,6 +91,9 @@ public abstract class Slot implements Serializable {
_uuid = UUID.fromString(obj.getString("uuid")); _uuid = UUID.fromString(obj.getString("uuid"));
} }
_encryptedMasterKey = Hex.toBytes(obj.getString("key")); _encryptedMasterKey = Hex.toBytes(obj.getString("key"));
} catch (JSONException | HexException e) {
throw new SlotException(e);
}
} }
public abstract byte getType(); public abstract byte getType();

View file

@ -10,17 +10,17 @@ import java.util.Arrays;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import me.impy.aegis.crypto.MasterKey; import me.impy.aegis.crypto.MasterKey;
import me.impy.aegis.encoding.Hex; import me.impy.aegis.encoding.Hex;
import me.impy.aegis.encoding.HexException;
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 JSONObject serialize(SlotCollection slots) throws JSONException { public static JSONObject serialize(SlotCollection slots) throws SlotCollectionException {
try {
JSONObject obj = new JSONObject(); JSONObject obj = new JSONObject();
obj.put("hash", Hex.toString(slots.getMasterHash())); obj.put("hash", Hex.toString(slots.getMasterHash()));
@ -31,11 +31,15 @@ public class SlotCollection implements Iterable<Slot>, Serializable {
obj.put("entries", entries); obj.put("entries", entries);
return obj; return obj;
} catch (SlotException | JSONException e) {
throw new SlotCollectionException(e);
}
} }
public static SlotCollection deserialize(JSONObject obj) throws Exception { public static SlotCollection deserialize(JSONObject obj) throws SlotCollectionException {
SlotCollection slots = new SlotCollection(); SlotCollection slots = new SlotCollection();
try {
byte[] masterHash = Hex.toBytes(obj.getString("hash")); byte[] masterHash = Hex.toBytes(obj.getString("hash"));
slots.setMasterHash(masterHash); slots.setMasterHash(masterHash);
@ -55,12 +59,15 @@ public class SlotCollection implements Iterable<Slot>, Serializable {
slot = new FingerprintSlot(); slot = new FingerprintSlot();
break; break;
default: default:
throw new Exception("unrecognized slot type"); throw new SlotException("unrecognized slot type");
} }
slot.deserialize(slotObj); slot.deserialize(slotObj);
slots.add(slot); slots.add(slot);
} }
} catch (SlotException | JSONException | HexException e) {
throw new SlotCollectionException(e);
}
return slots; return slots;
} }
@ -114,14 +121,12 @@ public class SlotCollection implements Iterable<Slot>, Serializable {
return _slots.iterator(); return _slots.iterator();
} }
public void encrypt(Slot slot, MasterKey key, Cipher cipher) public void encrypt(Slot slot, MasterKey key, Cipher cipher) throws SlotException {
throws BadPaddingException, IllegalBlockSizeException {
slot.setKey(key, cipher); slot.setKey(key, cipher);
setMasterHash(key.getHash()); setMasterHash(key.getHash());
} }
public MasterKey decrypt(Slot slot, Cipher cipher) public MasterKey decrypt(Slot slot, Cipher cipher) throws SlotException, SlotIntegrityException {
throws SlotIntegrityException, BadPaddingException, IllegalBlockSizeException {
byte[] hash = getMasterHash(); byte[] hash = getMasterHash();
MasterKey key = new MasterKey(slot.getKey(cipher)); MasterKey key = new MasterKey(slot.getKey(cipher));
if (!Arrays.equals(hash, key.getHash())) { if (!Arrays.equals(hash, key.getHash())) {

View file

@ -0,0 +1,11 @@
package me.impy.aegis.db.slots;
public class SlotCollectionException extends Exception {
public SlotCollectionException(Throwable cause) {
super(cause);
}
public SlotCollectionException(String message) {
super(message);
}
}

View file

@ -0,0 +1,11 @@
package me.impy.aegis.db.slots;
public class SlotException extends Exception {
public SlotException(Throwable cause) {
super(cause);
}
public SlotException(String message) {
super(message);
}
}

View file

@ -4,7 +4,9 @@ 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.DatabaseException;
import me.impy.aegis.db.DatabaseFile; import me.impy.aegis.db.DatabaseFile;
import me.impy.aegis.db.DatabaseFileException;
import me.impy.aegis.util.ByteInputStream; import me.impy.aegis.util.ByteInputStream;
public class AegisImporter extends DatabaseImporter { public class AegisImporter extends DatabaseImporter {
@ -14,13 +16,17 @@ public class AegisImporter extends DatabaseImporter {
} }
@Override @Override
public List<DatabaseEntry> convert() throws Exception { public List<DatabaseEntry> convert() throws DatabaseImporterException {
try {
byte[] bytes = _stream.getBytes(); byte[] bytes = _stream.getBytes();
DatabaseFile file = new DatabaseFile(); DatabaseFile file = new DatabaseFile();
file.deserialize(bytes); file.deserialize(bytes);
Database db = new Database(); Database db = new Database();
db.deserialize(file.getContent()); db.deserialize(file.getContent());
return db.getKeys(); return db.getKeys();
} catch (DatabaseFileException | DatabaseException e) {
throw new DatabaseImporterException(e);
}
} }
@Override @Override

View file

@ -1,5 +1,6 @@
package me.impy.aegis.importers; package me.impy.aegis.importers;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -19,14 +20,15 @@ public abstract class DatabaseImporter {
_stream = stream; _stream = stream;
} }
public abstract List<DatabaseEntry> convert() throws Exception; public abstract List<DatabaseEntry> convert() throws DatabaseImporterException;
public abstract String getName(); public abstract String getName();
public static DatabaseImporter create(ByteInputStream stream, Class<? extends DatabaseImporter> type) { public static DatabaseImporter create(ByteInputStream stream, Class<? extends DatabaseImporter> type) {
try { try {
return type.getConstructor(ByteInputStream.class).newInstance(stream); return type.getConstructor(ByteInputStream.class).newInstance(stream);
} catch (Exception e) { } catch (IllegalAccessException | InstantiationException
| NoSuchMethodException | InvocationTargetException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }

View file

@ -0,0 +1,7 @@
package me.impy.aegis.importers;
public class DatabaseImporterException extends Exception {
public DatabaseImporterException(Throwable cause) {
super(cause);
}
}

View file

@ -28,12 +28,16 @@ public class FreeOTPImporter extends DatabaseImporter {
} }
@Override @Override
public List<DatabaseEntry> convert() throws Exception { public List<DatabaseEntry> convert() throws DatabaseImporterException {
try {
XmlPullParser parser = Xml.newPullParser(); XmlPullParser parser = Xml.newPullParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
parser.setInput(_stream, null); parser.setInput(_stream, null);
parser.nextTag(); parser.nextTag();
return parse(parser); return parse(parser);
} catch (KeyInfoException | XmlPullParserException | JSONException | IOException e) {
throw new DatabaseImporterException(e);
}
} }
@Override @Override

View file

@ -21,11 +21,13 @@ import javax.crypto.SecretKey;
import me.impy.aegis.R; import me.impy.aegis.R;
import me.impy.aegis.crypto.KeyStoreHandle; import me.impy.aegis.crypto.KeyStoreHandle;
import me.impy.aegis.crypto.KeyStoreHandleException;
import me.impy.aegis.crypto.MasterKey; import me.impy.aegis.crypto.MasterKey;
import me.impy.aegis.db.slots.FingerprintSlot; import me.impy.aegis.db.slots.FingerprintSlot;
import me.impy.aegis.db.slots.PasswordSlot; import me.impy.aegis.db.slots.PasswordSlot;
import me.impy.aegis.db.slots.Slot; import me.impy.aegis.db.slots.Slot;
import me.impy.aegis.db.slots.SlotCollection; import me.impy.aegis.db.slots.SlotCollection;
import me.impy.aegis.db.slots.SlotException;
import me.impy.aegis.helpers.FingerprintHelper; import me.impy.aegis.helpers.FingerprintHelper;
import me.impy.aegis.helpers.FingerprintUiHelper; import me.impy.aegis.helpers.FingerprintUiHelper;
import me.impy.aegis.helpers.EditTextHelper; import me.impy.aegis.helpers.EditTextHelper;
@ -80,7 +82,7 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
break; break;
} }
} }
} catch (Exception e) { } catch (KeyStoreHandleException | SlotException e) {
throw new UndeclaredThrowableException(e); throw new UndeclaredThrowableException(e);
} }

View file

@ -18,6 +18,9 @@ import me.impy.aegis.AegisApplication;
import me.impy.aegis.R; import me.impy.aegis.R;
import me.impy.aegis.crypto.CryptResult; import me.impy.aegis.crypto.CryptResult;
import me.impy.aegis.crypto.MasterKey; import me.impy.aegis.crypto.MasterKey;
import me.impy.aegis.db.DatabaseException;
import me.impy.aegis.db.DatabaseFileException;
import me.impy.aegis.db.DatabaseManagerException;
import me.impy.aegis.db.slots.FingerprintSlot; import me.impy.aegis.db.slots.FingerprintSlot;
import me.impy.aegis.db.slots.PasswordSlot; import me.impy.aegis.db.slots.PasswordSlot;
import me.impy.aegis.db.slots.Slot; import me.impy.aegis.db.slots.Slot;
@ -25,6 +28,7 @@ import me.impy.aegis.db.slots.SlotCollection;
import me.impy.aegis.db.Database; import me.impy.aegis.db.Database;
import me.impy.aegis.db.DatabaseFile; import me.impy.aegis.db.DatabaseFile;
import me.impy.aegis.db.DatabaseManager; import me.impy.aegis.db.DatabaseManager;
import me.impy.aegis.db.slots.SlotException;
import me.impy.aegis.ui.slides.CustomAuthenticatedSlide; import me.impy.aegis.ui.slides.CustomAuthenticatedSlide;
import me.impy.aegis.ui.slides.CustomAuthenticationSlide; import me.impy.aegis.ui.slides.CustomAuthenticationSlide;
import me.impy.aegis.ui.tasks.DerivationTask; import me.impy.aegis.ui.tasks.DerivationTask;
@ -134,17 +138,11 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback {
// generate the master key // generate the master key
MasterKey masterKey = null; MasterKey masterKey = null;
if (cryptType != CustomAuthenticationSlide.CRYPT_TYPE_NONE) { if (cryptType != CustomAuthenticationSlide.CRYPT_TYPE_NONE) {
try {
masterKey = MasterKey.generate(); masterKey = MasterKey.generate();
} catch (Exception e) {
setException(e);
return;
}
} }
SlotCollection slots = _databaseFile.getSlots(); SlotCollection slots = _databaseFile.getSlots();
if (cryptType != CustomAuthenticationSlide.CRYPT_TYPE_NONE) { if (cryptType != CustomAuthenticationSlide.CRYPT_TYPE_NONE) {
try {
// encrypt the master key with a key derived from the user's password // encrypt the master key with a key derived from the user's password
// and add it to the list of slots // and add it to the list of slots
if (_passwordSlot == null || _passwordCipher == null) { if (_passwordSlot == null || _passwordCipher == null) {
@ -153,13 +151,9 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback {
try { try {
slots.encrypt(_passwordSlot, masterKey, _passwordCipher); slots.encrypt(_passwordSlot, masterKey, _passwordCipher);
slots.add(_passwordSlot); slots.add(_passwordSlot);
} catch (Exception e) { } catch (SlotException e) {
setException(e); setException(e);
} }
} catch (Exception e) {
setException(e);
return;
}
} }
if (cryptType == CustomAuthenticationSlide.CRYPT_TYPE_FINGER) { if (cryptType == CustomAuthenticationSlide.CRYPT_TYPE_FINGER) {
@ -170,7 +164,7 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback {
Cipher cipher = _authenticatedSlide.getFingerCipher(); Cipher cipher = _authenticatedSlide.getFingerCipher();
slots.encrypt(slot, masterKey, cipher); slots.encrypt(slot, masterKey, cipher);
slots.add(slot); slots.add(slot);
} catch (Exception e) { } catch (SlotException e) {
setException(e); setException(e);
return; return;
} }
@ -185,7 +179,7 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback {
_databaseFile.setContent(obj, masterKey); _databaseFile.setContent(obj, masterKey);
} }
DatabaseManager.save(getApplicationContext(), _databaseFile); DatabaseManager.save(getApplicationContext(), _databaseFile);
} catch (Exception e) { } catch (DatabaseException | DatabaseManagerException | DatabaseFileException e) {
setException(e); setException(e);
return; return;
} }
@ -205,7 +199,7 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback {
if (key != null) { if (key != null) {
try { try {
_passwordCipher = Slot.createCipher(key, Cipher.ENCRYPT_MODE); _passwordCipher = Slot.createCipher(key, Cipher.ENCRYPT_MODE);
} catch (Exception e) { } catch (SlotException e) {
setException(e); setException(e);
} }
} else { } else {

View file

@ -21,6 +21,8 @@ import android.widget.Toast;
import com.getbase.floatingactionbutton.FloatingActionsMenu; import com.getbase.floatingactionbutton.FloatingActionsMenu;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.reflect.UndeclaredThrowableException; import java.lang.reflect.UndeclaredThrowableException;
import java.util.List; import java.util.List;
@ -28,11 +30,13 @@ import java.util.List;
import me.impy.aegis.AegisApplication; import me.impy.aegis.AegisApplication;
import me.impy.aegis.R; import me.impy.aegis.R;
import me.impy.aegis.crypto.MasterKey; import me.impy.aegis.crypto.MasterKey;
import me.impy.aegis.db.DatabaseManagerException;
import me.impy.aegis.db.slots.SlotCollection; import me.impy.aegis.db.slots.SlotCollection;
import me.impy.aegis.db.DatabaseEntry; import me.impy.aegis.db.DatabaseEntry;
import me.impy.aegis.db.DatabaseManager; import me.impy.aegis.db.DatabaseManager;
import me.impy.aegis.helpers.PermissionHelper; import me.impy.aegis.helpers.PermissionHelper;
import me.impy.aegis.importers.DatabaseImporter; import me.impy.aegis.importers.DatabaseImporter;
import me.impy.aegis.importers.DatabaseImporterException;
import me.impy.aegis.ui.views.KeyProfile; import me.impy.aegis.ui.views.KeyProfile;
import me.impy.aegis.ui.views.KeyProfileView; import me.impy.aegis.ui.views.KeyProfileView;
import me.impy.aegis.util.ByteInputStream; import me.impy.aegis.util.ByteInputStream;
@ -107,10 +111,10 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
if (_db.isLocked()) { if (_db.isLocked()) {
startAuthActivity(); startAuthActivity();
} }
} catch (Exception e) { } catch (DatabaseManagerException e) {
e.printStackTrace(); e.printStackTrace();
Toast.makeText(this, "An error occurred while trying to deserialize the database", Toast.LENGTH_LONG).show(); Toast.makeText(this, "An error occurred while trying to deserialize the database", Toast.LENGTH_LONG).show();
throw new UndeclaredThrowableException(e); finish();
} }
} }
} }
@ -243,14 +247,7 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
onExport(); onExport();
break; break;
case PreferencesActivity.ACTION_SLOTS: case PreferencesActivity.ACTION_SLOTS:
MasterKey masterKey; MasterKey masterKey = _db.getMasterKey();
try {
masterKey = _db.getMasterKey();
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "An error occurred while trying to obtain the database key", Toast.LENGTH_SHORT).show();
break;
}
Intent intent = new Intent(this, SlotManagerActivity.class); Intent intent = new Intent(this, SlotManagerActivity.class);
intent.putExtra("masterKey", masterKey); intent.putExtra("masterKey", masterKey);
intent.putExtra("slots", _db.getFile().getSlots()); intent.putExtra("slots", _db.getFile().getSlots());
@ -272,7 +269,7 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
String filename; String filename;
try { try {
filename = _db.export(checked[0]); filename = _db.export(checked[0]);
} catch (Exception e) { } catch (DatabaseManagerException e) {
e.printStackTrace(); e.printStackTrace();
Toast.makeText(this, "An error occurred while trying to export the database", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "An error occurred while trying to export the database", Toast.LENGTH_SHORT).show();
return; return;
@ -314,8 +311,8 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
try { try {
try { try {
fileStream = getContentResolver().openInputStream(data.getData()); fileStream = getContentResolver().openInputStream(data.getData());
} catch (Exception e) { } catch (FileNotFoundException e) {
Toast.makeText(this, "An error occurred while trying to open the file", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "Error: File not found", Toast.LENGTH_SHORT).show();
return; return;
} }
@ -328,7 +325,7 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
outStream.write(buf, 0, read); outStream.write(buf, 0, read);
} }
stream = new ByteInputStream(outStream.toByteArray()); stream = new ByteInputStream(outStream.toByteArray());
} catch (Exception e) { } catch (IOException e) {
Toast.makeText(this, "An error occurred while trying to read the file", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "An error occurred while trying to read the file", Toast.LENGTH_SHORT).show();
return; return;
} }
@ -338,7 +335,7 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
try { try {
entries = converter.convert(); entries = converter.convert();
break; break;
} catch (Exception e) { } catch (DatabaseImporterException e) {
stream.reset(); stream.reset();
} }
} }
@ -355,7 +352,8 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
if (fileStream != null) { if (fileStream != null) {
try { try {
fileStream.close(); fileStream.close();
} catch (Exception e) { } catch (IOException e) {
e.printStackTrace();
} }
} }
} }
@ -405,13 +403,7 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
if (!data.getBooleanExtra("delete", false)) { if (!data.getBooleanExtra("delete", false)) {
// this profile has been serialized/deserialized and is no longer the same instance it once was // this profile has been serialized/deserialized and is no longer the same instance it once was
// to deal with this, the replaceKey functions are used // to deal with this, the replaceKey functions are used
try {
_db.replaceKey(profile.getEntry()); _db.replaceKey(profile.getEntry());
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "An error occurred while trying to update an entry", Toast.LENGTH_SHORT).show();
return;
}
_keyProfileView.replaceKey(profile); _keyProfileView.replaceKey(profile);
saveDatabase(); saveDatabase();
} else { } else {
@ -431,14 +423,7 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
private void addKey(KeyProfile profile) { private void addKey(KeyProfile profile) {
DatabaseEntry entry = profile.getEntry(); DatabaseEntry entry = profile.getEntry();
entry.setName(entry.getInfo().getAccountName()); entry.setName(entry.getInfo().getAccountName());
try {
_db.addKey(entry); _db.addKey(entry);
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "An error occurred while trying to add an entry", Toast.LENGTH_SHORT).show();
return;
}
_keyProfileView.addKey(profile); _keyProfileView.addKey(profile);
} }
@ -455,7 +440,7 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
if (_db.isLocked()) { if (_db.isLocked()) {
_db.unlock(key); _db.unlock(key);
} }
} catch (Exception e) { } catch (DatabaseManagerException e) {
e.printStackTrace(); e.printStackTrace();
Toast.makeText(this, "An error occurred while trying to load/decrypt the database", Toast.LENGTH_LONG).show(); Toast.makeText(this, "An error occurred while trying to load/decrypt the database", Toast.LENGTH_LONG).show();
startAuthActivity(); startAuthActivity();
@ -469,7 +454,7 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
MasterKey key = (MasterKey) intent.getSerializableExtra("key"); MasterKey key = (MasterKey) intent.getSerializableExtra("key");
try { try {
_db.unlock(key); _db.unlock(key);
} catch (Exception e) { } catch (DatabaseManagerException e) {
e.printStackTrace(); e.printStackTrace();
Toast.makeText(this, "An error occurred while trying to decrypt the database", Toast.LENGTH_LONG).show(); Toast.makeText(this, "An error occurred while trying to decrypt the database", Toast.LENGTH_LONG).show();
startAuthActivity(); startAuthActivity();
@ -564,13 +549,7 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
.setTitle("Delete entry") .setTitle("Delete entry")
.setMessage("Are you sure you want to delete this profile?") .setMessage("Are you sure you want to delete this profile?")
.setPositiveButton(android.R.string.yes, (dialog, which) -> { .setPositiveButton(android.R.string.yes, (dialog, which) -> {
try {
_db.removeKey(profile.getEntry()); _db.removeKey(profile.getEntry());
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "An error occurred while trying to delete an entry", Toast.LENGTH_SHORT).show();
return;
}
saveDatabase(); saveDatabase();
_keyProfileView.removeKey(profile); _keyProfileView.removeKey(profile);
@ -597,12 +576,7 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
return true; return true;
case R.id.action_lock: case R.id.action_lock:
_keyProfileView.clearKeys(); _keyProfileView.clearKeys();
try {
_db.lock(); _db.lock();
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "An error occurred while trying to lock the database", Toast.LENGTH_LONG).show();
}
startAuthActivity(); startAuthActivity();
return true; return true;
default: default:
@ -619,7 +593,7 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
private void saveDatabase() { private void saveDatabase() {
try { try {
_db.save(); _db.save();
} catch (Exception e) { } catch (DatabaseManagerException e) {
e.printStackTrace(); e.printStackTrace();
Toast.makeText(this, "An error occurred while trying to save the database", Toast.LENGTH_LONG).show(); Toast.makeText(this, "An error occurred while trying to save the database", Toast.LENGTH_LONG).show();
} }
@ -628,15 +602,9 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
private void loadKeyProfiles() { private void loadKeyProfiles() {
updateLockIcon(); updateLockIcon();
try {
for (DatabaseEntry entry : _db.getKeys()) { for (DatabaseEntry entry : _db.getKeys()) {
_keyProfileView.addKey(new KeyProfile(entry)); _keyProfileView.addKey(new KeyProfile(entry));
} }
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "An error occurred while trying to load database entries", Toast.LENGTH_SHORT).show();
return;
}
} }
private void updateLockIcon() { private void updateLockIcon() {
@ -654,12 +622,7 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
@Override @Override
public void onEntryMove(DatabaseEntry entry1, DatabaseEntry entry2) { public void onEntryMove(DatabaseEntry entry1, DatabaseEntry entry2) {
try {
_db.swapKeys(entry1, entry2); _db.swapKeys(entry1, entry2);
} catch (Exception e) {
e.printStackTrace();
throw new UndeclaredThrowableException(e);
}
} }
@Override @Override

View file

@ -1,6 +1,5 @@
package me.impy.aegis.ui; package me.impy.aegis.ui;
import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
@ -14,6 +13,7 @@ import java.util.Collections;
import me.dm7.barcodescanner.core.IViewFinder; import me.dm7.barcodescanner.core.IViewFinder;
import me.dm7.barcodescanner.zxing.ZXingScannerView; import me.dm7.barcodescanner.zxing.ZXingScannerView;
import me.impy.aegis.crypto.KeyInfo; import me.impy.aegis.crypto.KeyInfo;
import me.impy.aegis.crypto.KeyInfoException;
import me.impy.aegis.db.DatabaseEntry; import me.impy.aegis.db.DatabaseEntry;
import me.impy.aegis.helpers.SquareFinderView; import me.impy.aegis.helpers.SquareFinderView;
import me.impy.aegis.ui.views.KeyProfile; import me.impy.aegis.ui.views.KeyProfile;
@ -64,9 +64,9 @@ public class ScannerActivity extends AegisActivity implements ZXingScannerView.R
Intent resultIntent = new Intent(); Intent resultIntent = new Intent();
resultIntent.putExtra("KeyProfile", profile); resultIntent.putExtra("KeyProfile", profile);
setResult(Activity.RESULT_OK, resultIntent); setResult(RESULT_OK, resultIntent);
finish(); finish();
} catch (Exception e) { } catch (KeyInfoException e) {
Toast.makeText(this, "An error occurred while trying to parse the QR code contents", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "An error occurred while trying to parse the QR code contents", Toast.LENGTH_SHORT).show();
} }

View file

@ -21,6 +21,7 @@ import me.impy.aegis.db.slots.FingerprintSlot;
import me.impy.aegis.db.slots.PasswordSlot; import me.impy.aegis.db.slots.PasswordSlot;
import me.impy.aegis.db.slots.Slot; import me.impy.aegis.db.slots.Slot;
import me.impy.aegis.db.slots.SlotCollection; import me.impy.aegis.db.slots.SlotCollection;
import me.impy.aegis.db.slots.SlotException;
import me.impy.aegis.helpers.FingerprintHelper; import me.impy.aegis.helpers.FingerprintHelper;
import me.impy.aegis.ui.dialogs.FingerprintDialogFragment; import me.impy.aegis.ui.dialogs.FingerprintDialogFragment;
import me.impy.aegis.ui.dialogs.PasswordDialogFragment; import me.impy.aegis.ui.dialogs.PasswordDialogFragment;
@ -167,7 +168,7 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
public void onSlotResult(Slot slot, Cipher cipher) { public void onSlotResult(Slot slot, Cipher cipher) {
try { try {
_slots.encrypt(slot, _masterKey, cipher); _slots.encrypt(slot, _masterKey, cipher);
} catch (Exception e) { } catch (SlotException e) {
onException(e); onException(e);
return; return;
} }

View file

@ -15,8 +15,10 @@ import javax.crypto.SecretKey;
import me.impy.aegis.R; import me.impy.aegis.R;
import me.impy.aegis.crypto.KeyStoreHandle; import me.impy.aegis.crypto.KeyStoreHandle;
import me.impy.aegis.crypto.KeyStoreHandleException;
import me.impy.aegis.db.slots.FingerprintSlot; import me.impy.aegis.db.slots.FingerprintSlot;
import me.impy.aegis.db.slots.Slot; import me.impy.aegis.db.slots.Slot;
import me.impy.aegis.db.slots.SlotException;
import me.impy.aegis.helpers.FingerprintHelper; import me.impy.aegis.helpers.FingerprintHelper;
import me.impy.aegis.helpers.FingerprintUiHelper; import me.impy.aegis.helpers.FingerprintUiHelper;
@ -38,7 +40,7 @@ public class FingerprintDialogFragment extends SlotDialogFragment implements Fin
SecretKey key = new KeyStoreHandle().generateKey(_slot.getUUID().toString()); SecretKey key = new KeyStoreHandle().generateKey(_slot.getUUID().toString());
_cipher = Slot.createCipher(key, Cipher.ENCRYPT_MODE); _cipher = Slot.createCipher(key, Cipher.ENCRYPT_MODE);
_helper = new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, this); _helper = new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, this);
} catch (Exception e) { } catch (KeyStoreHandleException | SlotException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View file

@ -15,6 +15,7 @@ import javax.crypto.Cipher;
import me.impy.aegis.R; import me.impy.aegis.R;
import me.impy.aegis.db.slots.PasswordSlot; import me.impy.aegis.db.slots.PasswordSlot;
import me.impy.aegis.db.slots.Slot; import me.impy.aegis.db.slots.Slot;
import me.impy.aegis.db.slots.SlotException;
import me.impy.aegis.helpers.EditTextHelper; import me.impy.aegis.helpers.EditTextHelper;
import me.impy.aegis.ui.tasks.DerivationTask; import me.impy.aegis.ui.tasks.DerivationTask;
@ -51,7 +52,7 @@ public class PasswordDialogFragment extends SlotDialogFragment {
Cipher cipher; Cipher cipher;
try { try {
cipher = Slot.createCipher(key, Cipher.ENCRYPT_MODE); cipher = Slot.createCipher(key, Cipher.ENCRYPT_MODE);
} catch (Exception e) { } catch (SlotException e) {
getListener().onException(e); getListener().onException(e);
dialog.cancel(); dialog.cancel();
return; return;

View file

@ -25,6 +25,7 @@ import javax.crypto.SecretKey;
import me.impy.aegis.R; import me.impy.aegis.R;
import me.impy.aegis.crypto.KeyStoreHandle; import me.impy.aegis.crypto.KeyStoreHandle;
import me.impy.aegis.crypto.KeyStoreHandleException;
import me.impy.aegis.db.slots.FingerprintSlot; import me.impy.aegis.db.slots.FingerprintSlot;
import me.impy.aegis.db.slots.Slot; import me.impy.aegis.db.slots.Slot;
import me.impy.aegis.helpers.FingerprintUiHelper; import me.impy.aegis.helpers.FingerprintUiHelper;
@ -102,7 +103,7 @@ public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiH
_fingerSlot = new FingerprintSlot(); _fingerSlot = new FingerprintSlot();
} }
key = _storeHandle.generateKey(_fingerSlot.getUUID().toString()); key = _storeHandle.generateKey(_fingerSlot.getUUID().toString());
} catch (Exception e) { } catch (KeyStoreHandleException e) {
throw new UndeclaredThrowableException(e); throw new UndeclaredThrowableException(e);
} }

View file

@ -3,10 +3,14 @@ package me.impy.aegis.ui.tasks;
import android.content.Context; import android.content.Context;
import android.os.Process; import android.os.Process;
import java.security.NoSuchAlgorithmException;
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.db.slots.PasswordSlot; import me.impy.aegis.db.slots.PasswordSlot;
import me.impy.aegis.db.slots.SlotException;
public class DerivationTask extends ProgressDialogTask<DerivationTask.Params, SecretKey> { public class DerivationTask extends ProgressDialogTask<DerivationTask.Params, SecretKey> {
private Callback _cb; private Callback _cb;
@ -23,9 +27,8 @@ public class DerivationTask extends ProgressDialogTask<DerivationTask.Params, Se
DerivationTask.Params params = args[0]; DerivationTask.Params params = args[0];
try { try {
byte[] salt = CryptoUtils.generateSalt(); byte[] salt = CryptoUtils.generateSalt();
SecretKey key = params.Slot.deriveKey(params.Password, salt, CryptoUtils.CRYPTO_SCRYPT_N, CryptoUtils.CRYPTO_SCRYPT_r, CryptoUtils.CRYPTO_SCRYPT_p); return params.Slot.deriveKey(params.Password, salt, CryptoUtils.CRYPTO_SCRYPT_N, CryptoUtils.CRYPTO_SCRYPT_r, CryptoUtils.CRYPTO_SCRYPT_p);
return key; } catch (SlotException e) {
} catch (Exception e) {
return null; return null;
} }
} }

View file

@ -13,6 +13,7 @@ import me.impy.aegis.db.slots.FingerprintSlot;
import me.impy.aegis.db.slots.PasswordSlot; import me.impy.aegis.db.slots.PasswordSlot;
import me.impy.aegis.db.slots.Slot; import me.impy.aegis.db.slots.Slot;
import me.impy.aegis.db.slots.SlotCollection; import me.impy.aegis.db.slots.SlotCollection;
import me.impy.aegis.db.slots.SlotException;
import me.impy.aegis.db.slots.SlotIntegrityException; import me.impy.aegis.db.slots.SlotIntegrityException;
public class SlotCollectionTask<T extends Slot> extends ProgressDialogTask<SlotCollectionTask.Params, MasterKey> { public class SlotCollectionTask<T extends Slot> extends ProgressDialogTask<SlotCollectionTask.Params, MasterKey> {
@ -59,7 +60,7 @@ public class SlotCollectionTask<T extends Slot> extends ProgressDialogTask<SlotC
return masterKey; return masterKey;
} catch (SlotIntegrityException e) { } catch (SlotIntegrityException e) {
return null; return null;
} catch (Exception e) { } catch (SlotException e) {
throw new UndeclaredThrowableException(e); throw new UndeclaredThrowableException(e);
} }
} }

View file

@ -6,6 +6,7 @@ import java.io.Serializable;
import java.lang.reflect.UndeclaredThrowableException; import java.lang.reflect.UndeclaredThrowableException;
import me.impy.aegis.crypto.otp.OTP; import me.impy.aegis.crypto.otp.OTP;
import me.impy.aegis.crypto.otp.OTPException;
import me.impy.aegis.db.DatabaseEntry; import me.impy.aegis.db.DatabaseEntry;
import me.impy.aegis.helpers.TextDrawableHelper; import me.impy.aegis.helpers.TextDrawableHelper;
@ -31,7 +32,7 @@ public class KeyProfile implements Serializable {
public String refreshCode() { public String refreshCode() {
try { try {
_code = OTP.generateOTP(_entry.getInfo()); _code = OTP.generateOTP(_entry.getInfo());
} catch (Exception e) { } catch (OTPException e) {
throw new UndeclaredThrowableException(e); throw new UndeclaredThrowableException(e);
} }
return _code; return _code;