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 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 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 byte CRYPTO_TAG_SIZE = 16;
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_r = 8;
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[] keyBytes = SCrypt.generate(bytes, salt, n, r, p, CRYPTO_KEY_SIZE);
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();
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);
Cipher cipher = Cipher.getInstance(CRYPTO_CIPHER_AEAD);
cipher.init(opmode, key, spec);
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
byte[] result = cipher.doFinal(data);
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
ByteArrayOutputStream stream = new ByteArrayOutputStream();
stream.write(encrypted);
@ -94,7 +99,7 @@ public class CryptoUtils {
try {
hash = MessageDigest.getInstance(CRYPTO_HASH);
} catch (NoSuchAlgorithmException e) {
throw new UndeclaredThrowableException(e);
throw new AssertionError(e);
}
byte[] bytes = key.getEncoded();
@ -102,10 +107,14 @@ public class CryptoUtils {
return hash.digest();
}
public static SecretKey generateKey() throws NoSuchAlgorithmException {
KeyGenerator generator = KeyGenerator.getInstance("AES");
generator.init(CRYPTO_KEY_SIZE * 8);
return generator.generateKey();
public static SecretKey generateKey() {
try {
KeyGenerator generator = KeyGenerator.getInstance("AES");
generator.init(CRYPTO_KEY_SIZE * 8);
return generator.generateKey();
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}
public static byte[] generateSalt() {

View file

@ -7,10 +7,12 @@ import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
@ -40,27 +42,40 @@ public class KeyStoreHandle {
}
}
public SecretKey generateKey(String id) throws Exception {
if (isSupported()) {
public SecretKey generateKey(String id) throws KeyStoreHandleException {
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);
generator.init(new KeyGenParameterSpec.Builder(id,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_ECB)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setUserAuthenticationRequired(true)
.setRandomizedEncryptionRequired(false)
.setKeySize(CryptoUtils.CRYPTO_KEY_SIZE * 8)
.build());
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_ECB)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setUserAuthenticationRequired(true)
.setRandomizedEncryptionRequired(false)
.setKeySize(CryptoUtils.CRYPTO_KEY_SIZE * 8)
.build());
return generator.generateKey();
} else {
throw new Exception("Symmetric KeyStore keys are not supported in this version of Android");
} catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException e) {
throw new KeyStoreHandleException(e);
}
}
public SecretKey getKey(String id)
throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException {
SecretKey key = (SecretKey) _keyStore.getKey(id, null);
public SecretKey getKey(String id) throws KeyStoreHandleException {
SecretKey key;
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
// and see if KeyPermanentlyInvalidatedException is thrown
@ -71,7 +86,7 @@ public class KeyStoreHandle {
cipher.init(Cipher.ENCRYPT_MODE, key);
} catch (KeyPermanentlyInvalidatedException e) {
return null;
} catch (NoSuchPaddingException | InvalidKeyException e) {
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) {
throw new RuntimeException(e);
}
}
@ -79,8 +94,12 @@ public class KeyStoreHandle {
return key;
}
public void deleteKey(String id) throws KeyStoreException {
_keyStore.deleteEntry(id);
public void deleteKey(String id) throws KeyStoreHandleException {
try {
_keyStore.deleteEntry(id);
} catch (KeyStoreException e) {
throw new KeyStoreHandleException(e);
}
}
public static boolean isSupported() {

View file

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

View file

@ -22,22 +22,36 @@ public class MasterKey implements Serializable {
_key = key;
}
public static MasterKey generate() throws NoSuchAlgorithmException {
public static MasterKey generate() {
return new MasterKey(CryptoUtils.generateKey());
}
public CryptResult encrypt(byte[] bytes)
throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, InvalidKeyException,
NoSuchPaddingException, BadPaddingException, IllegalBlockSizeException {
Cipher cipher = CryptoUtils.createCipher(_key, Cipher.ENCRYPT_MODE);
return CryptoUtils.encrypt(bytes, cipher);
public CryptResult encrypt(byte[] bytes) throws MasterKeyException {
try {
Cipher cipher = CryptoUtils.createCipher(_key, Cipher.ENCRYPT_MODE);
return CryptoUtils.encrypt(bytes, cipher);
} catch (NoSuchPaddingException
| NoSuchAlgorithmException
| InvalidAlgorithmParameterException
| InvalidKeyException | BadPaddingException
| IllegalBlockSizeException e) {
throw new MasterKeyException(e);
}
}
public CryptResult decrypt(byte[] bytes, CryptParameters params)
throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, InvalidKeyException,
NoSuchPaddingException, BadPaddingException, IllegalBlockSizeException, IOException {
Cipher cipher = CryptoUtils.createCipher(_key, Cipher.DECRYPT_MODE, params.Nonce);
return CryptoUtils.decrypt(bytes, cipher, params);
public CryptResult decrypt(byte[] bytes, CryptParameters params) throws MasterKeyException {
try {
Cipher cipher = CryptoUtils.createCipher(_key, Cipher.DECRYPT_MODE, params.Nonce);
return CryptoUtils.decrypt(bytes, cipher, params);
} catch (NoSuchPaddingException
| NoSuchAlgorithmException
| InvalidAlgorithmParameterException
| InvalidKeyException
| BadPaddingException
| IOException
| IllegalBlockSizeException e) {
throw new MasterKeyException(e);
}
}
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,19 +9,23 @@ public class OTP {
private OTP() {
}
public static String generateOTP(KeyInfo info) throws InvalidKeyException, NoSuchAlgorithmException {
public static String generateOTP(KeyInfo info) throws OTPException {
String otp;
switch (info.getType()) {
case "totp":
String time = Long.toHexString(System.currentTimeMillis() / 1000 / info.getPeriod());
otp = TOTP.generateTOTP(info.getSecret(), time, info.getDigits(), info.getAlgorithm(true));
break;
case "hotp":
otp = HOTP.generateOTP(info.getSecret(), info.getCounter(), info.getDigits(), false, -1);
break;
default:
throw new RuntimeException();
try {
switch (info.getType()) {
case "totp":
String time = Long.toHexString(System.currentTimeMillis() / 1000 / info.getPeriod());
otp = TOTP.generateTOTP(info.getSecret(), time, info.getDigits(), info.getAlgorithm(true));
break;
case "hotp":
otp = HOTP.generateOTP(info.getSecret(), info.getCounter(), info.getDigits(), false, -1);
break;
default:
throw new RuntimeException("Bad OTP type");
}
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new OTPException(e);
}
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;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
@ -8,35 +9,45 @@ import java.util.Collections;
import java.util.List;
import java.util.UUID;
import me.impy.aegis.crypto.KeyInfoException;
public class Database {
private static final int VERSION = 1;
private List<DatabaseEntry> _entries = new ArrayList<>();
public JSONObject serialize() throws Exception {
JSONArray array = new JSONArray();
for (DatabaseEntry e : _entries) {
array.put(e.serialize());
}
public JSONObject serialize() throws DatabaseException {
try {
JSONArray array = new JSONArray();
for (DatabaseEntry e : _entries) {
array.put(e.serialize());
}
JSONObject obj = new JSONObject();
obj.put("version", VERSION);
obj.put("entries", array);
return obj;
JSONObject obj = new JSONObject();
obj.put("version", VERSION);
obj.put("entries", array);
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
int ver = obj.getInt("version");
if (ver != VERSION) {
throw new Exception("Unsupported version");
}
try {
int ver = obj.getInt("version");
if (ver != VERSION) {
throw new DatabaseException("Unsupported version");
}
JSONArray array = obj.getJSONArray("entries");
for (int i = 0; i < array.length(); i++) {
DatabaseEntry entry = new DatabaseEntry(null);
entry.deserialize(array.getJSONObject(i));
addKey(entry);
JSONArray array = obj.getJSONArray("entries");
for (int i = 0; i < array.length(); i++) {
DatabaseEntry entry = new DatabaseEntry(null);
entry.deserialize(array.getJSONObject(i));
addKey(entry);
}
} catch (JSONException | KeyInfoException e) {
throw new DatabaseException(e);
}
}

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.JSONObject;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import me.impy.aegis.crypto.CryptParameters;
import me.impy.aegis.crypto.CryptResult;
import me.impy.aegis.crypto.MasterKey;
import me.impy.aegis.crypto.MasterKeyException;
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.HexException;
public class DatabaseFile {
public static final byte VERSION = 1;
@ -32,53 +27,61 @@ public class DatabaseFile {
_slots = new SlotCollection();
}
public byte[] serialize() throws JSONException, UnsupportedEncodingException {
JSONObject cryptObj = null;
if (_cryptParameters != null) {
cryptObj = new JSONObject();
cryptObj.put("nonce", Hex.toString(_cryptParameters.Nonce));
cryptObj.put("tag", Hex.toString(_cryptParameters.Tag));
public byte[] serialize() throws DatabaseFileException {
try {
JSONObject cryptObj = null;
if (_cryptParameters != null) {
cryptObj = new JSONObject();
cryptObj.put("nonce", Hex.toString(_cryptParameters.Nonce));
cryptObj.put("tag", Hex.toString(_cryptParameters.Tag));
}
// don't write the crypt parameters if the content is not encrypted
boolean plain = _content instanceof JSONObject || _slots.isEmpty() || cryptObj == null;
JSONObject headerObj = new JSONObject();
headerObj.put("slots", plain ? JSONObject.NULL : SlotCollection.serialize(_slots));
headerObj.put("params", plain ? JSONObject.NULL : cryptObj);
JSONObject obj = new JSONObject();
obj.put("version", VERSION);
obj.put("header", headerObj);
obj.put("db", _content);
String string = obj.toString(4);
return string.getBytes("UTF-8");
} catch (SlotCollectionException | UnsupportedEncodingException | JSONException e) {
throw new DatabaseFileException(e);
}
// don't write the crypt parameters if the content is not encrypted
boolean plain = _content instanceof JSONObject || _slots.isEmpty() || cryptObj == null;
JSONObject headerObj = new JSONObject();
headerObj.put("slots", plain ? JSONObject.NULL : SlotCollection.serialize(_slots));
headerObj.put("params", plain ? JSONObject.NULL : cryptObj);
JSONObject obj = new JSONObject();
obj.put("version", VERSION);
obj.put("header", headerObj);
obj.put("db", _content);
String string = obj.toString(4);
return string.getBytes("UTF-8");
}
public void deserialize(byte[] data) throws Exception {
JSONObject obj = new JSONObject(new String(data, "UTF-8"));
JSONObject headerObj = obj.getJSONObject("header");
if (obj.getInt("version") > VERSION) {
throw new Exception("unsupported version");
}
public void deserialize(byte[] data) throws DatabaseFileException {
try {
JSONObject obj = new JSONObject(new String(data, "UTF-8"));
JSONObject headerObj = obj.getJSONObject("header");
if (obj.getInt("version") > VERSION) {
throw new DatabaseFileException("unsupported version");
}
JSONObject slotObj = headerObj.optJSONObject("slots");
if (slotObj != null) {
_slots = SlotCollection.deserialize(slotObj);
}
JSONObject slotObj = headerObj.optJSONObject("slots");
if (slotObj != null) {
_slots = SlotCollection.deserialize(slotObj);
}
JSONObject cryptObj = headerObj.optJSONObject("params");
if (cryptObj != null) {
_cryptParameters = new CryptParameters() {{
Nonce = Hex.toBytes(cryptObj.getString("nonce"));
Tag = Hex.toBytes(cryptObj.getString("tag"));
}};
}
JSONObject cryptObj = headerObj.optJSONObject("params");
if (cryptObj != null) {
_cryptParameters = new CryptParameters() {{
Nonce = Hex.toBytes(cryptObj.getString("nonce"));
Tag = Hex.toBytes(cryptObj.getString("tag"));
}};
}
if (cryptObj == null || slotObj == null) {
_content = obj.getJSONObject("db");
} else {
_content = obj.getString("db");
if (cryptObj == null || slotObj == null) {
_content = obj.getJSONObject("db");
} else {
_content = obj.getString("db");
}
} catch (SlotCollectionException | UnsupportedEncodingException | JSONException | HexException e) {
throw new DatabaseFileException(e);
}
}
@ -90,13 +93,14 @@ public class DatabaseFile {
return (JSONObject) _content;
}
public JSONObject getContent(MasterKey key)
throws NoSuchPaddingException, InvalidKeyException,
NoSuchAlgorithmException, IllegalBlockSizeException,
BadPaddingException, InvalidAlgorithmParameterException, IOException, JSONException {
byte[] bytes = Base64.decode((String) _content, Base64.NO_WRAP);
CryptResult result = key.decrypt(bytes, _cryptParameters);
return new JSONObject(new String(result.Data, "UTF-8"));
public JSONObject getContent(MasterKey key) throws DatabaseFileException {
try {
byte[] bytes = Base64.decode((String) _content, Base64.NO_WRAP);
CryptResult result = key.decrypt(bytes, _cryptParameters);
return new JSONObject(new String(result.Data, "UTF-8"));
} catch (MasterKeyException | JSONException | UnsupportedEncodingException e) {
throw new DatabaseFileException(e);
}
}
public void setContent(JSONObject dbObj) {
@ -104,16 +108,17 @@ public class DatabaseFile {
_cryptParameters = null;
}
public void setContent(JSONObject dbObj, MasterKey key)
throws JSONException, UnsupportedEncodingException,
NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException,
IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
String string = dbObj.toString(4);
byte[] dbBytes = string.getBytes("UTF-8");
public void setContent(JSONObject dbObj, MasterKey key) throws DatabaseFileException {
try {
String string = dbObj.toString(4);
byte[] dbBytes = string.getBytes("UTF-8");
CryptResult result = key.encrypt(dbBytes);
_content = new String(Base64.encode(result.Data, Base64.NO_WRAP), "UTF-8");
_cryptParameters = result.Parameters;
CryptResult result = key.encrypt(dbBytes);
_content = new String(Base64.encode(result.Data, Base64.NO_WRAP), "UTF-8");
_cryptParameters = result.Parameters;
} catch (MasterKeyException | UnsupportedEncodingException | JSONException e) {
throw new DatabaseFileException(e);
}
}
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.os.Environment;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.DataInputStream;
@ -35,137 +34,159 @@ public class DatabaseManager {
return file.exists() && file.isFile();
}
public void load() throws Exception {
public void load() throws DatabaseManagerException {
assertState(true, false);
byte[] fileBytes;
FileInputStream file = null;
try {
file = _context.openFileInput(FILENAME);
fileBytes = new byte[(int) file.getChannel().size()];
DataInputStream stream = new DataInputStream(file);
stream.readFully(fileBytes);
stream.close();
} finally {
// always close the file stream
// there is no need to close the DataInputStream
if (file != null) {
file.close();
byte[] fileBytes;
FileInputStream file = null;
try {
file = _context.openFileInput(FILENAME);
fileBytes = new byte[(int) file.getChannel().size()];
DataInputStream stream = new DataInputStream(file);
stream.readFully(fileBytes);
stream.close();
} finally {
// always close the file stream
// there is no need to close the DataInputStream
if (file != null) {
file.close();
}
}
}
_file = new DatabaseFile();
_file.deserialize(fileBytes);
_file = new DatabaseFile();
_file.deserialize(fileBytes);
if (!_file.isEncrypted()) {
JSONObject obj = _file.getContent();
_db = new Database();
_db.deserialize(obj);
if (!_file.isEncrypted()) {
JSONObject obj = _file.getContent();
_db = new Database();
_db.deserialize(obj);
}
} catch (IOException | DatabaseFileException | DatabaseException e) {
throw new DatabaseManagerException(e);
}
}
public void lock() throws Exception {
public void lock() {
assertState(false, true);
// TODO: properly clear everything
_key = null;
_db = null;
}
public void unlock(MasterKey key) throws Exception {
public void unlock(MasterKey key) throws DatabaseManagerException {
assertState(true, true);
JSONObject obj = _file.getContent(key);
_db = new Database();
_db.deserialize(obj);
_key = key;
}
public static void save(Context context, DatabaseFile file) throws IOException, JSONException {
byte[] bytes = file.serialize();
FileOutputStream stream = null;
try {
stream = context.openFileOutput(FILENAME, Context.MODE_PRIVATE);
stream.write(bytes);
} finally {
// always close the file stream
if (stream != null) {
stream.close();
JSONObject obj = _file.getContent(key);
_db = new Database();
_db.deserialize(obj);
_key = key;
} catch (DatabaseFileException | DatabaseException e) {
throw new DatabaseManagerException(e);
}
}
public static void save(Context context, DatabaseFile file) throws DatabaseManagerException {
try {
byte[] bytes = file.serialize();
FileOutputStream stream = null;
try {
stream = context.openFileOutput(FILENAME, Context.MODE_PRIVATE);
stream.write(bytes);
} finally {
// always close the file stream
if (stream != null) {
stream.close();
}
}
} catch (IOException | DatabaseFileException e) {
throw new DatabaseManagerException(e);
}
}
public void save() throws Exception {
assertState(false, true);
JSONObject obj = _db.serialize();
if (_file.isEncrypted()) {
_file.setContent(obj, _key);
} else {
_file.setContent(obj);
}
save(_context, _file);
}
public String export(boolean encrypt) throws Exception {
public void save() throws DatabaseManagerException {
assertState(false, true);
DatabaseFile dbFile = new DatabaseFile();
dbFile.setSlots(_file.getSlots());
if (encrypt && getFile().isEncrypted()) {
dbFile.setContent(_db.serialize(), _key);
} else {
dbFile.setContent(_db.serialize());
}
File file;
FileOutputStream stream = null;
try {
String dirName = !BuildConfig.DEBUG ? "Aegis" : "AegisDebug";
File dir = new File(Environment.getExternalStorageDirectory(), dirName);
if (!dir.exists() && !dir.mkdirs()) {
throw new IOException("error creating external storage directory");
}
byte[] bytes = dbFile.serialize();
file = new File(dir.getAbsolutePath(), encrypt ? FILENAME_EXPORT : FILENAME_EXPORT_PLAIN);
stream = new FileOutputStream(file);
stream.write(bytes);
} finally {
// always close the file stream
if (stream != null) {
stream.close();
JSONObject obj = _db.serialize();
if (_file.isEncrypted()) {
_file.setContent(obj, _key);
} else {
_file.setContent(obj);
}
save(_context, _file);
} catch (DatabaseException | DatabaseFileException e) {
throw new DatabaseManagerException(e);
}
return file.getAbsolutePath();
}
public void addKey(DatabaseEntry entry) throws Exception {
public String export(boolean encrypt) throws DatabaseManagerException {
assertState(false, true);
try {
DatabaseFile dbFile = new DatabaseFile();
dbFile.setSlots(_file.getSlots());
if (encrypt && getFile().isEncrypted()) {
dbFile.setContent(_db.serialize(), _key);
} else {
dbFile.setContent(_db.serialize());
}
File file;
FileOutputStream stream = null;
try {
String dirName = !BuildConfig.DEBUG ? "Aegis" : "AegisDebug";
File dir = new File(Environment.getExternalStorageDirectory(), dirName);
if (!dir.exists() && !dir.mkdirs()) {
throw new IOException("error creating external storage directory");
}
byte[] bytes = dbFile.serialize();
file = new File(dir.getAbsolutePath(), encrypt ? FILENAME_EXPORT : FILENAME_EXPORT_PLAIN);
stream = new FileOutputStream(file);
stream.write(bytes);
} finally {
// always close the file stream
if (stream != null) {
stream.close();
}
}
return file.getAbsolutePath();
} catch (DatabaseException | IOException | DatabaseFileException e) {
throw new DatabaseManagerException(e);
}
}
public void addKey(DatabaseEntry entry) {
assertState(false, true);
_db.addKey(entry);
}
public void removeKey(DatabaseEntry entry) throws Exception {
public void removeKey(DatabaseEntry entry) {
assertState(false, true);
_db.removeKey(entry);
}
public void replaceKey(DatabaseEntry entry) throws Exception {
public void replaceKey(DatabaseEntry entry) {
assertState(false, true);
_db.replaceKey(entry);
}
public void swapKeys(DatabaseEntry entry1, DatabaseEntry entry2) throws Exception {
public void swapKeys(DatabaseEntry entry1, DatabaseEntry entry2) {
assertState(false, true);
_db.swapKeys(entry1, entry2);
}
public List<DatabaseEntry> getKeys() throws Exception {
public List<DatabaseEntry> getKeys() {
assertState(false, true);
return _db.getKeys();
}
public MasterKey getMasterKey() throws Exception {
public MasterKey getMasterKey() {
assertState(false, true);
return _key;
}
@ -182,17 +203,17 @@ public class DatabaseManager {
return _db == null;
}
private void assertState(boolean locked, boolean loaded) throws Exception {
private void assertState(boolean locked, boolean 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) {
throw new Exception("database file has is already been loaded");
throw new AssertionError("database file has is already been loaded");
}
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) {
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.encoding.Hex;
import me.impy.aegis.encoding.HexException;
public class PasswordSlot extends RawSlot {
private int _n;
@ -22,35 +23,51 @@ public class PasswordSlot extends RawSlot {
}
@Override
public JSONObject serialize() throws JSONException {
JSONObject obj = super.serialize();
obj.put("n", _n);
obj.put("r", _r);
obj.put("p", _p);
obj.put("salt", Hex.toString(_salt));
return obj;
public JSONObject serialize() throws SlotException {
try {
JSONObject obj = super.serialize();
obj.put("n", _n);
obj.put("r", _r);
obj.put("p", _p);
obj.put("salt", Hex.toString(_salt));
return obj;
} catch (JSONException e) {
throw new SlotException(e);
}
}
@Override
public void deserialize(JSONObject obj) throws Exception {
super.deserialize(obj);
_n = obj.getInt("n");
_r = obj.getInt("r");
_p = obj.getInt("p");
_salt = Hex.toBytes(obj.getString("salt"));
public void deserialize(JSONObject obj) throws SlotException {
try {
super.deserialize(obj);
_n = obj.getInt("n");
_r = obj.getInt("r");
_p = obj.getInt("p");
_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 {
SecretKey key = CryptoUtils.deriveKey(password, salt, n, r, p);
_n = n;
_r = r;
_p = p;
_salt = salt;
return key;
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);
_n = n;
_r = r;
_p = p;
_salt = salt;
return key;
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new SlotException(e);
}
}
public SecretKey deriveKey(char[] password) throws InvalidKeySpecException, NoSuchAlgorithmException {
return CryptoUtils.deriveKey(password, _salt, _n, _r, _p);
public SecretKey deriveKey(char[] password) throws SlotException {
try {
return CryptoUtils.deriveKey(password, _salt, _n, _r, _p);
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
throw new SlotException(e);
}
}
@Override

View file

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

View file

@ -10,56 +10,63 @@ import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import me.impy.aegis.crypto.MasterKey;
import me.impy.aegis.encoding.Hex;
import me.impy.aegis.encoding.HexException;
public class SlotCollection implements Iterable<Slot>, Serializable {
private List<Slot> _slots = new ArrayList<>();
private byte[] _masterHash;
public static JSONObject serialize(SlotCollection slots) throws JSONException {
JSONObject obj = new JSONObject();
obj.put("hash", Hex.toString(slots.getMasterHash()));
public static JSONObject serialize(SlotCollection slots) throws SlotCollectionException {
try {
JSONObject obj = new JSONObject();
obj.put("hash", Hex.toString(slots.getMasterHash()));
JSONArray entries = new JSONArray();
for (Slot slot : slots) {
entries.put(slot.serialize());
}
obj.put("entries", entries);
return obj;
}
public static SlotCollection deserialize(JSONObject obj) throws Exception {
SlotCollection slots = new SlotCollection();
byte[] masterHash = Hex.toBytes(obj.getString("hash"));
slots.setMasterHash(masterHash);
JSONArray entries = obj.getJSONArray("entries");
for (int i = 0; i < entries.length(); i++) {
Slot slot;
JSONObject slotObj = entries.getJSONObject(i);
switch (slotObj.getInt("type")) {
case Slot.TYPE_RAW:
slot = new RawSlot();
break;
case Slot.TYPE_DERIVED:
slot = new PasswordSlot();
break;
case Slot.TYPE_FINGERPRINT:
slot = new FingerprintSlot();
break;
default:
throw new Exception("unrecognized slot type");
JSONArray entries = new JSONArray();
for (Slot slot : slots) {
entries.put(slot.serialize());
}
slot.deserialize(slotObj);
slots.add(slot);
obj.put("entries", entries);
return obj;
} catch (SlotException | JSONException e) {
throw new SlotCollectionException(e);
}
}
public static SlotCollection deserialize(JSONObject obj) throws SlotCollectionException {
SlotCollection slots = new SlotCollection();
try {
byte[] masterHash = Hex.toBytes(obj.getString("hash"));
slots.setMasterHash(masterHash);
JSONArray entries = obj.getJSONArray("entries");
for (int i = 0; i < entries.length(); i++) {
Slot slot;
JSONObject slotObj = entries.getJSONObject(i);
switch (slotObj.getInt("type")) {
case Slot.TYPE_RAW:
slot = new RawSlot();
break;
case Slot.TYPE_DERIVED:
slot = new PasswordSlot();
break;
case Slot.TYPE_FINGERPRINT:
slot = new FingerprintSlot();
break;
default:
throw new SlotException("unrecognized slot type");
}
slot.deserialize(slotObj);
slots.add(slot);
}
} catch (SlotException | JSONException | HexException e) {
throw new SlotCollectionException(e);
}
return slots;
@ -114,14 +121,12 @@ public class SlotCollection implements Iterable<Slot>, Serializable {
return _slots.iterator();
}
public void encrypt(Slot slot, MasterKey key, Cipher cipher)
throws BadPaddingException, IllegalBlockSizeException {
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 SlotIntegrityException, BadPaddingException, IllegalBlockSizeException {
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())) {

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

View file

@ -1,5 +1,6 @@
package me.impy.aegis.importers;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -19,14 +20,15 @@ public abstract class DatabaseImporter {
_stream = stream;
}
public abstract List<DatabaseEntry> convert() throws Exception;
public abstract List<DatabaseEntry> convert() throws DatabaseImporterException;
public abstract String getName();
public static DatabaseImporter create(ByteInputStream stream, Class<? extends DatabaseImporter> type) {
try {
return type.getConstructor(ByteInputStream.class).newInstance(stream);
} catch (Exception e) {
} catch (IllegalAccessException | InstantiationException
| NoSuchMethodException | InvocationTargetException 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
public List<DatabaseEntry> convert() throws Exception {
XmlPullParser parser = Xml.newPullParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
parser.setInput(_stream, null);
parser.nextTag();
return parse(parser);
public List<DatabaseEntry> convert() throws DatabaseImporterException {
try {
XmlPullParser parser = Xml.newPullParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
parser.setInput(_stream, null);
parser.nextTag();
return parse(parser);
} catch (KeyInfoException | XmlPullParserException | JSONException | IOException e) {
throw new DatabaseImporterException(e);
}
}
@Override

View file

@ -21,11 +21,13 @@ import javax.crypto.SecretKey;
import me.impy.aegis.R;
import me.impy.aegis.crypto.KeyStoreHandle;
import me.impy.aegis.crypto.KeyStoreHandleException;
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.SlotException;
import me.impy.aegis.helpers.FingerprintHelper;
import me.impy.aegis.helpers.FingerprintUiHelper;
import me.impy.aegis.helpers.EditTextHelper;
@ -80,7 +82,7 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
break;
}
}
} catch (Exception e) {
} catch (KeyStoreHandleException | SlotException 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.crypto.CryptResult;
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.PasswordSlot;
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.DatabaseFile;
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.CustomAuthenticationSlide;
import me.impy.aegis.ui.tasks.DerivationTask;
@ -134,31 +138,21 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback {
// generate the master key
MasterKey masterKey = null;
if (cryptType != CustomAuthenticationSlide.CRYPT_TYPE_NONE) {
try {
masterKey = MasterKey.generate();
} catch (Exception e) {
setException(e);
return;
}
masterKey = MasterKey.generate();
}
SlotCollection slots = _databaseFile.getSlots();
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
if (_passwordSlot == null || _passwordCipher == null) {
throw new RuntimeException();
}
try {
// encrypt the master key with a key derived from the user's password
// and add it to the list of slots
if (_passwordSlot == null || _passwordCipher == null) {
throw new RuntimeException();
}
try {
slots.encrypt(_passwordSlot, masterKey, _passwordCipher);
slots.add(_passwordSlot);
} catch (Exception e) {
setException(e);
}
} catch (Exception e) {
slots.encrypt(_passwordSlot, masterKey, _passwordCipher);
slots.add(_passwordSlot);
} catch (SlotException e) {
setException(e);
return;
}
}
@ -170,7 +164,7 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback {
Cipher cipher = _authenticatedSlide.getFingerCipher();
slots.encrypt(slot, masterKey, cipher);
slots.add(slot);
} catch (Exception e) {
} catch (SlotException e) {
setException(e);
return;
}
@ -185,7 +179,7 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback {
_databaseFile.setContent(obj, masterKey);
}
DatabaseManager.save(getApplicationContext(), _databaseFile);
} catch (Exception e) {
} catch (DatabaseException | DatabaseManagerException | DatabaseFileException e) {
setException(e);
return;
}
@ -205,7 +199,7 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback {
if (key != null) {
try {
_passwordCipher = Slot.createCipher(key, Cipher.ENCRYPT_MODE);
} catch (Exception e) {
} catch (SlotException e) {
setException(e);
}
} else {

View file

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

View file

@ -1,6 +1,5 @@
package me.impy.aegis.ui;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
@ -14,6 +13,7 @@ import java.util.Collections;
import me.dm7.barcodescanner.core.IViewFinder;
import me.dm7.barcodescanner.zxing.ZXingScannerView;
import me.impy.aegis.crypto.KeyInfo;
import me.impy.aegis.crypto.KeyInfoException;
import me.impy.aegis.db.DatabaseEntry;
import me.impy.aegis.helpers.SquareFinderView;
import me.impy.aegis.ui.views.KeyProfile;
@ -64,9 +64,9 @@ public class ScannerActivity extends AegisActivity implements ZXingScannerView.R
Intent resultIntent = new Intent();
resultIntent.putExtra("KeyProfile", profile);
setResult(Activity.RESULT_OK, resultIntent);
setResult(RESULT_OK, resultIntent);
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();
}

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.Slot;
import me.impy.aegis.db.slots.SlotCollection;
import me.impy.aegis.db.slots.SlotException;
import me.impy.aegis.helpers.FingerprintHelper;
import me.impy.aegis.ui.dialogs.FingerprintDialogFragment;
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) {
try {
_slots.encrypt(slot, _masterKey, cipher);
} catch (Exception e) {
} catch (SlotException e) {
onException(e);
return;
}

View file

@ -15,8 +15,10 @@ import javax.crypto.SecretKey;
import me.impy.aegis.R;
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.Slot;
import me.impy.aegis.db.slots.SlotException;
import me.impy.aegis.helpers.FingerprintHelper;
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());
_cipher = Slot.createCipher(key, Cipher.ENCRYPT_MODE);
_helper = new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, this);
} catch (Exception e) {
} catch (KeyStoreHandleException | SlotException e) {
throw new RuntimeException(e);
}

View file

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

View file

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

View file

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

View file

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