mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-05-14 14:02:49 +00:00
Make sure we only catch specific exceptions instead of catching them all
This commit is contained in:
parent
ebf06aca01
commit
f1a03638a0
33 changed files with 608 additions and 425 deletions
|
@ -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() {
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package me.impy.aegis.crypto;
|
||||||
|
|
||||||
|
public class MasterKeyException extends Exception {
|
||||||
|
public MasterKeyException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package me.impy.aegis.crypto.otp;
|
||||||
|
|
||||||
|
public class OTPException extends Exception {
|
||||||
|
public OTPException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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) {
|
||||||
|
|
11
app/src/main/java/me/impy/aegis/db/DatabaseException.java
Normal file
11
app/src/main/java/me/impy/aegis/db/DatabaseException.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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() {
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package me.impy.aegis.db;
|
||||||
|
|
||||||
|
public class DatabaseManagerException extends Exception {
|
||||||
|
public DatabaseManagerException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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())) {
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
11
app/src/main/java/me/impy/aegis/db/slots/SlotException.java
Normal file
11
app/src/main/java/me/impy/aegis/db/slots/SlotException.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package me.impy.aegis.importers;
|
||||||
|
|
||||||
|
public class DatabaseImporterException extends Exception {
|
||||||
|
public DatabaseImporterException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue