mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-05-14 05:52:52 +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 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() {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -4,4 +4,8 @@ public class KeyStoreHandleException extends Exception {
|
|||
public KeyStoreHandleException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public KeyStoreHandleException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package me.impy.aegis.crypto;
|
||||
|
||||
public class MasterKeyException extends Exception {
|
||||
public MasterKeyException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
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.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() {
|
||||
|
|
|
@ -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.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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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())) {
|
||||
|
|
|
@ -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.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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue