mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-05-14 14:02:49 +00:00
Use GCM instead of a hash to check master key integrity
This is the last database format change before the initial release, probably
This commit is contained in:
parent
da37b5175e
commit
c3f94b37c8
21 changed files with 211 additions and 214 deletions
|
@ -1,6 +1,36 @@
|
||||||
package me.impy.aegis.crypto;
|
package me.impy.aegis.crypto;
|
||||||
|
|
||||||
public class CryptParameters {
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import me.impy.aegis.encoding.Hex;
|
||||||
|
import me.impy.aegis.encoding.HexException;
|
||||||
|
|
||||||
|
public class CryptParameters implements Serializable {
|
||||||
public byte[] Nonce;
|
public byte[] Nonce;
|
||||||
public byte[] Tag;
|
public byte[] Tag;
|
||||||
|
|
||||||
|
public JSONObject toJson() {
|
||||||
|
JSONObject obj = new JSONObject();
|
||||||
|
|
||||||
|
try {
|
||||||
|
obj.put("nonce", Hex.encode(Nonce));
|
||||||
|
obj.put("tag", Hex.encode(Tag));
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CryptParameters parseJson(JSONObject obj) throws JSONException, HexException {
|
||||||
|
byte[] tag = Hex.decode(obj.getString("tag"));
|
||||||
|
byte[] nonce = Hex.decode(obj.getString("nonce"));
|
||||||
|
return new CryptParameters() {{
|
||||||
|
Tag = tag;
|
||||||
|
Nonce = nonce;
|
||||||
|
}};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,16 +2,13 @@ package me.impy.aegis.crypto;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.UndeclaredThrowableException;
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.CharBuffer;
|
import java.nio.CharBuffer;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.spec.InvalidKeySpecException;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import javax.crypto.BadPaddingException;
|
import javax.crypto.BadPaddingException;
|
||||||
|
@ -20,20 +17,16 @@ import javax.crypto.IllegalBlockSizeException;
|
||||||
import javax.crypto.KeyGenerator;
|
import javax.crypto.KeyGenerator;
|
||||||
import javax.crypto.NoSuchPaddingException;
|
import javax.crypto.NoSuchPaddingException;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
import javax.crypto.spec.GCMParameterSpec;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
import org.spongycastle.crypto.generators.SCrypt;
|
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_AEAD = "AES/GCM/NoPadding";
|
||||||
|
public static final byte CRYPTO_AEAD_KEY_SIZE = 32;
|
||||||
public static final String CRYPTO_CIPHER_RAW = "AES/ECB/NoPadding";
|
public static final byte CRYPTO_AEAD_TAG_SIZE = 16;
|
||||||
public static final byte CRYPTO_KEY_SIZE = 32;
|
public static final byte CRYPTO_AEAD_NONCE_SIZE = 12;
|
||||||
|
|
||||||
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 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;
|
||||||
|
@ -41,23 +34,36 @@ public class CryptoUtils {
|
||||||
|
|
||||||
public static SecretKey deriveKey(char[] password, byte[] salt, int n, int r, int p) {
|
public static SecretKey deriveKey(char[] password, byte[] salt, int n, int r, int p) {
|
||||||
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_AEAD_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)
|
public static Cipher createEncryptCipher(SecretKey key)
|
||||||
throws NoSuchPaddingException, NoSuchAlgorithmException,
|
throws NoSuchPaddingException, NoSuchAlgorithmException,
|
||||||
InvalidAlgorithmParameterException, InvalidKeyException {
|
InvalidAlgorithmParameterException, InvalidKeyException {
|
||||||
byte[] nonce = generateNonce();
|
return createCipher(key, Cipher.ENCRYPT_MODE, null);
|
||||||
return createCipher(key, opmode, nonce);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Cipher createCipher(SecretKey key, int opmode, byte[] nonce)
|
public static Cipher createDecryptCipher(SecretKey key, byte[] nonce)
|
||||||
|
throws InvalidAlgorithmParameterException, NoSuchAlgorithmException,
|
||||||
|
InvalidKeyException, NoSuchPaddingException {
|
||||||
|
return createCipher(key, Cipher.DECRYPT_MODE, nonce);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Cipher createCipher(SecretKey key, int opmode, byte[] nonce)
|
||||||
throws NoSuchPaddingException, NoSuchAlgorithmException,
|
throws NoSuchPaddingException, NoSuchAlgorithmException,
|
||||||
InvalidAlgorithmParameterException, InvalidKeyException {
|
InvalidAlgorithmParameterException, InvalidKeyException {
|
||||||
IvParameterSpec spec = new IvParameterSpec(nonce);
|
Cipher cipher = Cipher.getInstance(CRYPTO_AEAD);
|
||||||
Cipher cipher = Cipher.getInstance(CRYPTO_CIPHER_AEAD);
|
|
||||||
cipher.init(opmode, key, spec);
|
// generate the nonce if none is given
|
||||||
|
// we are not allowed to do this ourselves as "setRandomizedEncryptionRequired" is set to true
|
||||||
|
if (nonce != null) {
|
||||||
|
GCMParameterSpec spec = new GCMParameterSpec(CRYPTO_AEAD_TAG_SIZE * 8, nonce);
|
||||||
|
cipher.init(opmode, key, spec);
|
||||||
|
} else {
|
||||||
|
cipher.init(opmode, key);
|
||||||
|
}
|
||||||
|
|
||||||
return cipher;
|
return cipher;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,8 +71,8 @@ public class CryptoUtils {
|
||||||
throws BadPaddingException, IllegalBlockSizeException {
|
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_AEAD_TAG_SIZE, result.length);
|
||||||
byte[] encrypted = Arrays.copyOfRange(result, 0, result.length - CRYPTO_TAG_SIZE);
|
byte[] encrypted = Arrays.copyOfRange(result, 0, result.length - CRYPTO_AEAD_TAG_SIZE);
|
||||||
|
|
||||||
return new CryptResult() {{
|
return new CryptResult() {{
|
||||||
Parameters = new CryptParameters() {{
|
Parameters = new CryptParameters() {{
|
||||||
|
@ -93,23 +99,10 @@ public class CryptoUtils {
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] hashKey(SecretKey key) {
|
|
||||||
MessageDigest hash;
|
|
||||||
try {
|
|
||||||
hash = MessageDigest.getInstance(CRYPTO_HASH);
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] bytes = key.getEncoded();
|
|
||||||
hash.update(bytes);
|
|
||||||
return hash.digest();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SecretKey generateKey() {
|
public static SecretKey generateKey() {
|
||||||
try {
|
try {
|
||||||
KeyGenerator generator = KeyGenerator.getInstance("AES");
|
KeyGenerator generator = KeyGenerator.getInstance("AES");
|
||||||
generator.init(CRYPTO_KEY_SIZE * 8);
|
generator.init(CRYPTO_AEAD_KEY_SIZE * 8);
|
||||||
return generator.generateKey();
|
return generator.generateKey();
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
|
@ -117,11 +110,7 @@ public class CryptoUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] generateSalt() {
|
public static byte[] generateSalt() {
|
||||||
return generateRandomBytes(CRYPTO_KEY_SIZE);
|
return generateRandomBytes(CRYPTO_AEAD_KEY_SIZE);
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] generateNonce() {
|
|
||||||
return generateRandomBytes(CRYPTO_NONCE_SIZE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] generateRandomBytes(int length) {
|
public static byte[] generateRandomBytes(int length) {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package me.impy.aegis.crypto;
|
package me.impy.aegis.crypto;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.security.keystore.KeyGenParameterSpec;
|
import android.security.keystore.KeyGenParameterSpec;
|
||||||
import android.security.keystore.KeyPermanentlyInvalidatedException;
|
import android.security.keystore.KeyPermanentlyInvalidatedException;
|
||||||
|
@ -51,11 +50,11 @@ public class KeyStoreHandle {
|
||||||
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)
|
||||||
.setBlockModes(KeyProperties.BLOCK_MODE_ECB)
|
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
|
||||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
||||||
.setUserAuthenticationRequired(true)
|
.setUserAuthenticationRequired(true)
|
||||||
.setRandomizedEncryptionRequired(false)
|
.setRandomizedEncryptionRequired(true)
|
||||||
.setKeySize(CryptoUtils.CRYPTO_KEY_SIZE * 8)
|
.setKeySize(CryptoUtils.CRYPTO_AEAD_KEY_SIZE * 8)
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
return generator.generateKey();
|
return generator.generateKey();
|
||||||
|
@ -81,8 +80,7 @@ public class KeyStoreHandle {
|
||||||
// and see if KeyPermanentlyInvalidatedException is thrown
|
// and see if KeyPermanentlyInvalidatedException is thrown
|
||||||
if (isSupported()) {
|
if (isSupported()) {
|
||||||
try {
|
try {
|
||||||
@SuppressLint("GetInstance")
|
Cipher cipher = Cipher.getInstance(CryptoUtils.CRYPTO_AEAD);
|
||||||
Cipher cipher = Cipher.getInstance(CryptoUtils.CRYPTO_CIPHER_RAW);
|
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, key);
|
cipher.init(Cipher.ENCRYPT_MODE, key);
|
||||||
} catch (KeyPermanentlyInvalidatedException e) {
|
} catch (KeyPermanentlyInvalidatedException e) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -28,7 +28,7 @@ public class MasterKey implements Serializable {
|
||||||
|
|
||||||
public CryptResult encrypt(byte[] bytes) throws MasterKeyException {
|
public CryptResult encrypt(byte[] bytes) throws MasterKeyException {
|
||||||
try {
|
try {
|
||||||
Cipher cipher = CryptoUtils.createCipher(_key, Cipher.ENCRYPT_MODE);
|
Cipher cipher = CryptoUtils.createEncryptCipher(_key);
|
||||||
return CryptoUtils.encrypt(bytes, cipher);
|
return CryptoUtils.encrypt(bytes, cipher);
|
||||||
} catch (NoSuchPaddingException
|
} catch (NoSuchPaddingException
|
||||||
| NoSuchAlgorithmException
|
| NoSuchAlgorithmException
|
||||||
|
@ -42,7 +42,7 @@ public class MasterKey implements Serializable {
|
||||||
|
|
||||||
public CryptResult decrypt(byte[] bytes, CryptParameters params) throws MasterKeyException {
|
public CryptResult decrypt(byte[] bytes, CryptParameters params) throws MasterKeyException {
|
||||||
try {
|
try {
|
||||||
Cipher cipher = CryptoUtils.createCipher(_key, Cipher.DECRYPT_MODE, params.Nonce);
|
Cipher cipher = CryptoUtils.createDecryptCipher(_key, params.Nonce);
|
||||||
return CryptoUtils.decrypt(bytes, cipher, params);
|
return CryptoUtils.decrypt(bytes, cipher, params);
|
||||||
} catch (NoSuchPaddingException
|
} catch (NoSuchPaddingException
|
||||||
| NoSuchAlgorithmException
|
| NoSuchAlgorithmException
|
||||||
|
@ -55,10 +55,6 @@ public class MasterKey implements Serializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getHash() {
|
|
||||||
return CryptoUtils.hashKey(_key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getBytes() {
|
public byte[] getBytes() {
|
||||||
return _key.getEncoded();
|
return _key.getEncoded();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package me.impy.aegis.db;
|
package me.impy.aegis.db;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
@ -9,11 +10,10 @@ import me.impy.aegis.crypto.CryptParameters;
|
||||||
import me.impy.aegis.crypto.CryptResult;
|
import me.impy.aegis.crypto.CryptResult;
|
||||||
import me.impy.aegis.crypto.MasterKey;
|
import me.impy.aegis.crypto.MasterKey;
|
||||||
import me.impy.aegis.crypto.MasterKeyException;
|
import me.impy.aegis.crypto.MasterKeyException;
|
||||||
import me.impy.aegis.db.slots.SlotCollection;
|
import me.impy.aegis.db.slots.SlotList;
|
||||||
import me.impy.aegis.db.slots.SlotCollectionException;
|
import me.impy.aegis.db.slots.SlotListException;
|
||||||
import me.impy.aegis.encoding.Base64;
|
import me.impy.aegis.encoding.Base64;
|
||||||
import me.impy.aegis.encoding.Base64Exception;
|
import me.impy.aegis.encoding.Base64Exception;
|
||||||
import me.impy.aegis.encoding.Hex;
|
|
||||||
import me.impy.aegis.encoding.HexException;
|
import me.impy.aegis.encoding.HexException;
|
||||||
|
|
||||||
public class DatabaseFile {
|
public class DatabaseFile {
|
||||||
|
@ -21,22 +21,15 @@ public class DatabaseFile {
|
||||||
|
|
||||||
private Object _content;
|
private Object _content;
|
||||||
private CryptParameters _cryptParameters;
|
private CryptParameters _cryptParameters;
|
||||||
private SlotCollection _slots;
|
private SlotList _slots;
|
||||||
|
|
||||||
public byte[] serialize() {
|
public byte[] serialize() {
|
||||||
try {
|
try {
|
||||||
JSONObject cryptObj = null;
|
// don't write the crypt parameters and slots if the content is not encrypted
|
||||||
if (isEncrypted()) {
|
boolean plain = _content instanceof JSONObject || !isEncrypted();
|
||||||
cryptObj = new JSONObject();
|
|
||||||
cryptObj.put("nonce", Hex.encode(_cryptParameters.Nonce));
|
|
||||||
cryptObj.put("tag", Hex.encode(_cryptParameters.Tag));
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't write the crypt parameters if the content is not encrypted
|
|
||||||
boolean plain = _content instanceof JSONObject || _slots == null || cryptObj == null;
|
|
||||||
JSONObject headerObj = new JSONObject();
|
JSONObject headerObj = new JSONObject();
|
||||||
headerObj.put("slots", plain ? JSONObject.NULL : SlotCollection.serialize(_slots));
|
headerObj.put("slots", plain ? JSONObject.NULL : SlotList.serialize(_slots));
|
||||||
headerObj.put("params", plain ? JSONObject.NULL : cryptObj);
|
headerObj.put("params", plain ? JSONObject.NULL : _cryptParameters.toJson());
|
||||||
|
|
||||||
JSONObject obj = new JSONObject();
|
JSONObject obj = new JSONObject();
|
||||||
obj.put("version", VERSION);
|
obj.put("version", VERSION);
|
||||||
|
@ -58,17 +51,14 @@ public class DatabaseFile {
|
||||||
throw new DatabaseFileException("unsupported version");
|
throw new DatabaseFileException("unsupported version");
|
||||||
}
|
}
|
||||||
|
|
||||||
JSONObject slotObj = headerObj.optJSONObject("slots");
|
JSONArray slotObj = headerObj.optJSONArray("slots");
|
||||||
if (slotObj != null) {
|
if (slotObj != null) {
|
||||||
_slots = SlotCollection.deserialize(slotObj);
|
_slots = SlotList.deserialize(slotObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
JSONObject cryptObj = headerObj.optJSONObject("params");
|
JSONObject cryptObj = headerObj.optJSONObject("params");
|
||||||
if (cryptObj != null) {
|
if (cryptObj != null) {
|
||||||
_cryptParameters = new CryptParameters() {{
|
_cryptParameters = CryptParameters.parseJson(cryptObj);
|
||||||
Nonce = Hex.decode(cryptObj.getString("nonce"));
|
|
||||||
Tag = Hex.decode(cryptObj.getString("tag"));
|
|
||||||
}};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cryptObj == null || slotObj == null) {
|
if (cryptObj == null || slotObj == null) {
|
||||||
|
@ -76,7 +66,7 @@ public class DatabaseFile {
|
||||||
} else {
|
} else {
|
||||||
_content = obj.getString("db");
|
_content = obj.getString("db");
|
||||||
}
|
}
|
||||||
} catch (SlotCollectionException | UnsupportedEncodingException | JSONException | HexException e) {
|
} catch (SlotListException | UnsupportedEncodingException | JSONException | HexException e) {
|
||||||
throw new DatabaseFileException(e);
|
throw new DatabaseFileException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,11 +108,11 @@ public class DatabaseFile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public SlotCollection getSlots() {
|
public SlotList getSlots() {
|
||||||
return _slots;
|
return _slots;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSlots(SlotCollection slots) {
|
public void setSlots(SlotList slots) {
|
||||||
_slots = slots;
|
_slots = slots;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import java.util.List;
|
||||||
|
|
||||||
import me.impy.aegis.BuildConfig;
|
import me.impy.aegis.BuildConfig;
|
||||||
import me.impy.aegis.crypto.MasterKey;
|
import me.impy.aegis.crypto.MasterKey;
|
||||||
import me.impy.aegis.db.slots.SlotCollection;
|
import me.impy.aegis.db.slots.SlotList;
|
||||||
|
|
||||||
public class DatabaseManager {
|
public class DatabaseManager {
|
||||||
private static final String FILENAME = "aegis.json";
|
private static final String FILENAME = "aegis.json";
|
||||||
|
@ -196,7 +196,7 @@ public class DatabaseManager {
|
||||||
return _file;
|
return _file;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void enableEncryption(MasterKey key, SlotCollection slots) {
|
public void enableEncryption(MasterKey key, SlotList slots) {
|
||||||
assertState(false, true);
|
assertState(false, true);
|
||||||
_key = key;
|
_key = key;
|
||||||
_file.setSlots(slots);
|
_file.setSlots(slots);
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
package me.impy.aegis.db.slots;
|
package me.impy.aegis.db.slots;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import javax.crypto.AEADBadTagException;
|
||||||
import javax.crypto.BadPaddingException;
|
import javax.crypto.BadPaddingException;
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.IllegalBlockSizeException;
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
|
@ -17,6 +18,8 @@ import javax.crypto.NoSuchPaddingException;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
import me.impy.aegis.crypto.CryptParameters;
|
||||||
|
import me.impy.aegis.crypto.CryptResult;
|
||||||
import me.impy.aegis.crypto.CryptoUtils;
|
import me.impy.aegis.crypto.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;
|
||||||
|
@ -29,40 +32,55 @@ public abstract class Slot implements Serializable {
|
||||||
|
|
||||||
protected UUID _uuid;
|
protected UUID _uuid;
|
||||||
protected byte[] _encryptedMasterKey;
|
protected byte[] _encryptedMasterKey;
|
||||||
|
protected CryptParameters _encryptedMasterKeyParams;
|
||||||
|
|
||||||
protected Slot() {
|
protected Slot() {
|
||||||
_uuid = UUID.randomUUID();
|
_uuid = UUID.randomUUID();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 using the given cipher and returns it.
|
||||||
public SecretKey getKey(Cipher cipher) throws SlotException {
|
public MasterKey getKey(Cipher cipher) throws SlotException, SlotIntegrityException {
|
||||||
try {
|
try {
|
||||||
byte[] decryptedKeyBytes = cipher.doFinal(_encryptedMasterKey);
|
CryptResult res = CryptoUtils.decrypt(_encryptedMasterKey, cipher, _encryptedMasterKeyParams);
|
||||||
return new SecretKeySpec(decryptedKeyBytes, CryptoUtils.CRYPTO_CIPHER_AEAD);
|
SecretKey key = new SecretKeySpec(res.Data, CryptoUtils.CRYPTO_AEAD);
|
||||||
} catch (BadPaddingException | IllegalBlockSizeException e) {
|
return new MasterKey(key);
|
||||||
|
} catch (AEADBadTagException e) {
|
||||||
|
throw new SlotIntegrityException(e);
|
||||||
|
} catch (IOException | BadPaddingException | IllegalBlockSizeException e) {
|
||||||
throw new SlotException(e);
|
throw new SlotException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// setKey encrypts the given master key with the given key and stores the result in this slot.
|
// setKey encrypts the given master key using the given cipher and stores the result in this slot.
|
||||||
public void setKey(MasterKey masterKey, Cipher cipher) throws SlotException {
|
public void setKey(MasterKey masterKey, Cipher cipher) throws SlotException {
|
||||||
try {
|
try {
|
||||||
byte[] masterKeyBytes = masterKey.getBytes();
|
byte[] masterKeyBytes = masterKey.getBytes();
|
||||||
_encryptedMasterKey = cipher.doFinal(masterKeyBytes);
|
CryptResult res = CryptoUtils.encrypt(masterKeyBytes, cipher);
|
||||||
|
_encryptedMasterKey = res.Data;
|
||||||
|
_encryptedMasterKeyParams = res.Parameters;
|
||||||
} catch (BadPaddingException | IllegalBlockSizeException e) {
|
} catch (BadPaddingException | IllegalBlockSizeException e) {
|
||||||
throw new SlotException(e);
|
throw new SlotException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// suppress the AES ECB warning
|
public static Cipher createEncryptCipher(SecretKey key) throws SlotException {
|
||||||
// this is perfectly safe because we discard this cipher after passing CryptoUtils.CRYPTO_KEY_SIZE bytes through it
|
|
||||||
@SuppressLint("getInstance")
|
|
||||||
public static Cipher createCipher(SecretKey key, int mode) throws SlotException {
|
|
||||||
try {
|
try {
|
||||||
Cipher cipher = Cipher.getInstance(CryptoUtils.CRYPTO_CIPHER_RAW);
|
return CryptoUtils.createEncryptCipher(key);
|
||||||
cipher.init(mode, key);
|
} catch (InvalidAlgorithmParameterException
|
||||||
return cipher;
|
| NoSuchPaddingException
|
||||||
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) {
|
| NoSuchAlgorithmException
|
||||||
|
| InvalidKeyException e) {
|
||||||
|
throw new SlotException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cipher createDecryptCipher(SecretKey key) throws SlotException {
|
||||||
|
try {
|
||||||
|
return CryptoUtils.createDecryptCipher(key, _encryptedMasterKeyParams.Nonce);
|
||||||
|
} catch (InvalidAlgorithmParameterException
|
||||||
|
| NoSuchAlgorithmException
|
||||||
|
| InvalidKeyException
|
||||||
|
| NoSuchPaddingException e) {
|
||||||
throw new SlotException(e);
|
throw new SlotException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,9 +88,11 @@ public abstract class Slot implements Serializable {
|
||||||
public JSONObject serialize() {
|
public JSONObject serialize() {
|
||||||
try {
|
try {
|
||||||
JSONObject obj = new JSONObject();
|
JSONObject obj = new JSONObject();
|
||||||
|
JSONObject paramObj = _encryptedMasterKeyParams.toJson();
|
||||||
obj.put("type", getType());
|
obj.put("type", getType());
|
||||||
obj.put("uuid", _uuid.toString());
|
obj.put("uuid", _uuid.toString());
|
||||||
obj.put("key", Hex.encode(_encryptedMasterKey));
|
obj.put("key", Hex.encode(_encryptedMasterKey));
|
||||||
|
obj.put("key_params", paramObj);
|
||||||
return obj;
|
return obj;
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
@ -84,13 +104,17 @@ public abstract class Slot implements Serializable {
|
||||||
if (obj.getInt("type") != getType()) {
|
if (obj.getInt("type") != getType()) {
|
||||||
throw new SlotException("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")) {
|
||||||
_uuid = UUID.randomUUID();
|
_uuid = UUID.randomUUID();
|
||||||
} else {
|
} else {
|
||||||
_uuid = UUID.fromString(obj.getString("uuid"));
|
_uuid = UUID.fromString(obj.getString("uuid"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JSONObject paramObj = obj.getJSONObject("key_params");
|
||||||
_encryptedMasterKey = Hex.decode(obj.getString("key"));
|
_encryptedMasterKey = Hex.decode(obj.getString("key"));
|
||||||
|
_encryptedMasterKeyParams = CryptParameters.parseJson(paramObj);
|
||||||
} catch (JSONException | HexException e) {
|
} catch (JSONException | HexException e) {
|
||||||
throw new SlotException(e);
|
throw new SlotException(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
package me.impy.aegis.db.slots;
|
|
||||||
|
|
||||||
public class SlotCollectionException extends Exception {
|
|
||||||
public SlotCollectionException(Throwable cause) {
|
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SlotCollectionException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,11 @@
|
||||||
package me.impy.aegis.db.slots;
|
package me.impy.aegis.db.slots;
|
||||||
|
|
||||||
public class SlotIntegrityException extends Exception {
|
public class SlotIntegrityException extends Exception {
|
||||||
|
public SlotIntegrityException() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public SlotIntegrityException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,47 +6,28 @@ import org.json.JSONObject;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
public class SlotList implements Iterable<Slot>, Serializable {
|
||||||
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 List<Slot> _slots = new ArrayList<>();
|
||||||
private byte[] _masterHash;
|
|
||||||
|
|
||||||
public static JSONObject serialize(SlotCollection slots) {
|
public static JSONArray serialize(SlotList slots) {
|
||||||
try {
|
JSONArray array = new JSONArray();
|
||||||
JSONObject obj = new JSONObject();
|
for (Slot slot : slots) {
|
||||||
obj.put("hash", Hex.encode(slots.getMasterHash()));
|
array.put(slot.serialize());
|
||||||
|
|
||||||
JSONArray entries = new JSONArray();
|
|
||||||
for (Slot slot : slots) {
|
|
||||||
entries.put(slot.serialize());
|
|
||||||
}
|
|
||||||
|
|
||||||
obj.put("entries", entries);
|
|
||||||
return obj;
|
|
||||||
} catch (JSONException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SlotCollection deserialize(JSONObject obj) throws SlotCollectionException {
|
public static SlotList deserialize(JSONArray array) throws SlotListException {
|
||||||
SlotCollection slots = new SlotCollection();
|
SlotList slots = new SlotList();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
byte[] masterHash = Hex.decode(obj.getString("hash"));
|
for (int i = 0; i < array.length(); i++) {
|
||||||
slots.setMasterHash(masterHash);
|
|
||||||
|
|
||||||
JSONArray entries = obj.getJSONArray("entries");
|
|
||||||
for (int i = 0; i < entries.length(); i++) {
|
|
||||||
Slot slot;
|
Slot slot;
|
||||||
JSONObject slotObj = entries.getJSONObject(i);
|
JSONObject slotObj = array.getJSONObject(i);
|
||||||
|
|
||||||
switch (slotObj.getInt("type")) {
|
switch (slotObj.getInt("type")) {
|
||||||
case Slot.TYPE_RAW:
|
case Slot.TYPE_RAW:
|
||||||
|
@ -65,8 +46,8 @@ public class SlotCollection implements Iterable<Slot>, Serializable {
|
||||||
slot.deserialize(slotObj);
|
slot.deserialize(slotObj);
|
||||||
slots.add(slot);
|
slots.add(slot);
|
||||||
}
|
}
|
||||||
} catch (SlotException | JSONException | HexException e) {
|
} catch (SlotException | JSONException e) {
|
||||||
throw new SlotCollectionException(e);
|
throw new SlotListException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return slots;
|
return slots;
|
||||||
|
@ -116,26 +97,4 @@ public class SlotCollection implements Iterable<Slot>, Serializable {
|
||||||
public Iterator<Slot> iterator() {
|
public Iterator<Slot> iterator() {
|
||||||
return _slots.iterator();
|
return _slots.iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void encrypt(Slot slot, MasterKey key, Cipher cipher) throws SlotException {
|
|
||||||
slot.setKey(key, cipher);
|
|
||||||
setMasterHash(key.getHash());
|
|
||||||
}
|
|
||||||
|
|
||||||
public MasterKey decrypt(Slot slot, Cipher cipher) throws SlotException, SlotIntegrityException {
|
|
||||||
byte[] hash = getMasterHash();
|
|
||||||
MasterKey key = new MasterKey(slot.getKey(cipher));
|
|
||||||
if (!Arrays.equals(hash, key.getHash())) {
|
|
||||||
throw new SlotIntegrityException();
|
|
||||||
}
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setMasterHash(byte[] masterHash) {
|
|
||||||
_masterHash = masterHash;
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] getMasterHash() {
|
|
||||||
return _masterHash;
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package me.impy.aegis.db.slots;
|
||||||
|
|
||||||
|
public class SlotListException extends Exception {
|
||||||
|
public SlotListException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SlotListException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,7 +26,7 @@ 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.SlotList;
|
||||||
import me.impy.aegis.db.slots.SlotException;
|
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;
|
||||||
|
@ -36,7 +36,7 @@ import me.impy.aegis.ui.tasks.SlotCollectionTask;
|
||||||
public class AuthActivity extends AegisActivity implements FingerprintUiHelper.Callback, SlotCollectionTask.Callback {
|
public class AuthActivity extends AegisActivity implements FingerprintUiHelper.Callback, SlotCollectionTask.Callback {
|
||||||
private EditText _textPassword;
|
private EditText _textPassword;
|
||||||
|
|
||||||
private SlotCollection _slots;
|
private SlotList _slots;
|
||||||
private FingerprintUiHelper _fingerHelper;
|
private FingerprintUiHelper _fingerHelper;
|
||||||
private Cipher _fingerCipher;
|
private Cipher _fingerCipher;
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
|
||||||
}
|
}
|
||||||
|
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
_slots = (SlotCollection) intent.getSerializableExtra("slots");
|
_slots = (SlotList) intent.getSerializableExtra("slots");
|
||||||
|
|
||||||
// only show the fingerprint controls if the api version is new enough, permission is granted, a scanner is found and a fingerprint slot is found
|
// only show the fingerprint controls if the api version is new enough, permission is granted, a scanner is found and a fingerprint slot is found
|
||||||
FingerprintManager manager = FingerprintHelper.getManager(this);
|
FingerprintManager manager = FingerprintHelper.getManager(this);
|
||||||
|
@ -75,7 +75,7 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
|
||||||
invalidated = true;
|
invalidated = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
_fingerCipher = Slot.createCipher(key, Cipher.DECRYPT_MODE);
|
_fingerCipher = slot.createDecryptCipher(key);
|
||||||
_fingerHelper = new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, this);
|
_fingerHelper = new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, this);
|
||||||
boxFingerprint.setVisibility(View.VISIBLE);
|
boxFingerprint.setVisibility(View.VISIBLE);
|
||||||
invalidated = false;
|
invalidated = false;
|
||||||
|
|
|
@ -23,7 +23,7 @@ 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;
|
||||||
import me.impy.aegis.db.slots.SlotCollection;
|
import me.impy.aegis.db.slots.SlotList;
|
||||||
import me.impy.aegis.db.Database;
|
import me.impy.aegis.db.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;
|
||||||
|
@ -145,7 +145,7 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback {
|
||||||
masterKey = MasterKey.generate();
|
masterKey = MasterKey.generate();
|
||||||
}
|
}
|
||||||
|
|
||||||
SlotCollection slots = null;
|
SlotList slots = null;
|
||||||
if (cryptType != CustomAuthenticationSlide.CRYPT_TYPE_NONE) {
|
if (cryptType != CustomAuthenticationSlide.CRYPT_TYPE_NONE) {
|
||||||
// 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
|
||||||
|
@ -153,8 +153,8 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback {
|
||||||
throw new RuntimeException();
|
throw new RuntimeException();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
slots = new SlotCollection();
|
_passwordSlot.setKey(masterKey, _passwordCipher);
|
||||||
slots.encrypt(_passwordSlot, masterKey, _passwordCipher);
|
slots = new SlotList();
|
||||||
slots.add(_passwordSlot);
|
slots.add(_passwordSlot);
|
||||||
_databaseFile.setSlots(slots);
|
_databaseFile.setSlots(slots);
|
||||||
} catch (SlotException e) {
|
} catch (SlotException e) {
|
||||||
|
@ -168,7 +168,7 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback {
|
||||||
// and add it to the list of slots
|
// and add it to the list of slots
|
||||||
FingerprintSlot slot = _authenticatedSlide.getFingerSlot();
|
FingerprintSlot slot = _authenticatedSlide.getFingerSlot();
|
||||||
Cipher cipher = _authenticatedSlide.getFingerCipher();
|
Cipher cipher = _authenticatedSlide.getFingerCipher();
|
||||||
slots.encrypt(slot, masterKey, cipher);
|
slot.setKey(masterKey, cipher);
|
||||||
slots.add(slot);
|
slots.add(slot);
|
||||||
} catch (SlotException e) {
|
} catch (SlotException e) {
|
||||||
setException(e);
|
setException(e);
|
||||||
|
@ -204,7 +204,7 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback {
|
||||||
public void onTaskFinished(SecretKey key) {
|
public void onTaskFinished(SecretKey key) {
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
try {
|
try {
|
||||||
_passwordCipher = Slot.createCipher(key, Cipher.ENCRYPT_MODE);
|
_passwordCipher = Slot.createEncryptCipher(key);
|
||||||
} catch (SlotException e) {
|
} catch (SlotException e) {
|
||||||
setException(e);
|
setException(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ import me.impy.aegis.db.DatabaseEntry;
|
||||||
import me.impy.aegis.db.DatabaseManager;
|
import me.impy.aegis.db.DatabaseManager;
|
||||||
import me.impy.aegis.db.DatabaseManagerException;
|
import me.impy.aegis.db.DatabaseManagerException;
|
||||||
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.SlotList;
|
||||||
import me.impy.aegis.db.slots.SlotException;
|
import me.impy.aegis.db.slots.SlotException;
|
||||||
import me.impy.aegis.helpers.PermissionHelper;
|
import me.impy.aegis.helpers.PermissionHelper;
|
||||||
import me.impy.aegis.importers.AegisImporter;
|
import me.impy.aegis.importers.AegisImporter;
|
||||||
|
@ -369,7 +369,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SlotCollection slots = (SlotCollection) data.getSerializableExtra("slots");
|
SlotList slots = (SlotList) data.getSerializableExtra("slots");
|
||||||
_db.getFile().setSlots(slots);
|
_db.getFile().setSlots(slots);
|
||||||
saveDatabase();
|
saveDatabase();
|
||||||
}
|
}
|
||||||
|
@ -390,9 +390,9 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
|
||||||
public void onSlotResult(Slot slot, Cipher cipher) {
|
public void onSlotResult(Slot slot, Cipher cipher) {
|
||||||
MasterKey masterKey = MasterKey.generate();
|
MasterKey masterKey = MasterKey.generate();
|
||||||
|
|
||||||
SlotCollection slots = new SlotCollection();
|
SlotList slots = new SlotList();
|
||||||
try {
|
try {
|
||||||
slots.encrypt(slot, masterKey, cipher);
|
slot.setKey(masterKey, cipher);
|
||||||
} catch (SlotException e) {
|
} catch (SlotException e) {
|
||||||
onException(e);
|
onException(e);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -20,7 +20,7 @@ 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.SlotList;
|
||||||
import me.impy.aegis.db.slots.SlotException;
|
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.Dialogs;
|
import me.impy.aegis.ui.dialogs.Dialogs;
|
||||||
|
@ -31,7 +31,7 @@ import me.impy.aegis.ui.dialogs.SlotDialogFragment;
|
||||||
|
|
||||||
public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Listener, SlotDialogFragment.Listener {
|
public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Listener, SlotDialogFragment.Listener {
|
||||||
private MasterKey _masterKey;
|
private MasterKey _masterKey;
|
||||||
private SlotCollection _slots;
|
private SlotList _slots;
|
||||||
private SlotAdapter _adapter;
|
private SlotAdapter _adapter;
|
||||||
|
|
||||||
private boolean _edited = false;
|
private boolean _edited = false;
|
||||||
|
@ -65,7 +65,7 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
|
||||||
|
|
||||||
// load the slots and masterKey
|
// load the slots and masterKey
|
||||||
_masterKey = (MasterKey) getIntent().getSerializableExtra("masterKey");
|
_masterKey = (MasterKey) getIntent().getSerializableExtra("masterKey");
|
||||||
_slots = (SlotCollection) getIntent().getSerializableExtra("slots");
|
_slots = (SlotList) getIntent().getSerializableExtra("slots");
|
||||||
for (Slot slot : _slots) {
|
for (Slot slot : _slots) {
|
||||||
_adapter.addSlot(slot);
|
_adapter.addSlot(slot);
|
||||||
}
|
}
|
||||||
|
@ -176,7 +176,7 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
|
||||||
@Override
|
@Override
|
||||||
public void onSlotResult(Slot slot, Cipher cipher) {
|
public void onSlotResult(Slot slot, Cipher cipher) {
|
||||||
try {
|
try {
|
||||||
_slots.encrypt(slot, _masterKey, cipher);
|
slot.setKey(_masterKey, cipher);
|
||||||
} catch (SlotException e) {
|
} catch (SlotException e) {
|
||||||
onException(e);
|
onException(e);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -38,7 +38,7 @@ public class FingerprintDialogFragment extends SlotDialogFragment implements Fin
|
||||||
try {
|
try {
|
||||||
_slot = new FingerprintSlot();
|
_slot = new FingerprintSlot();
|
||||||
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.createEncryptCipher(key);
|
||||||
_helper = new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, this);
|
_helper = new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, this);
|
||||||
} catch (KeyStoreHandleException | SlotException e) {
|
} catch (KeyStoreHandleException | SlotException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
|
|
@ -51,7 +51,7 @@ public class PasswordDialogFragment extends SlotDialogFragment {
|
||||||
DerivationTask task = new DerivationTask(getActivity(), key -> {
|
DerivationTask task = new DerivationTask(getActivity(), key -> {
|
||||||
Cipher cipher;
|
Cipher cipher;
|
||||||
try {
|
try {
|
||||||
cipher = Slot.createCipher(key, Cipher.ENCRYPT_MODE);
|
cipher = Slot.createEncryptCipher(key);
|
||||||
} catch (SlotException e) {
|
} catch (SlotException e) {
|
||||||
getListener().onException(e);
|
getListener().onException(e);
|
||||||
dialog.cancel();
|
dialog.cancel();
|
||||||
|
|
|
@ -113,7 +113,7 @@ public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiH
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
_fingerCipher = Slot.createCipher(key, Cipher.ENCRYPT_MODE);
|
_fingerCipher = Slot.createEncryptCipher(key);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new UndeclaredThrowableException(e);
|
throw new UndeclaredThrowableException(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,6 @@ package me.impy.aegis.ui.tasks;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import java.lang.reflect.UndeclaredThrowableException;
|
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
|
|
||||||
|
@ -11,7 +9,7 @@ import me.impy.aegis.crypto.MasterKey;
|
||||||
import me.impy.aegis.db.slots.FingerprintSlot;
|
import me.impy.aegis.db.slots.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.SlotList;
|
||||||
import me.impy.aegis.db.slots.SlotException;
|
import me.impy.aegis.db.slots.SlotException;
|
||||||
import me.impy.aegis.db.slots.SlotIntegrityException;
|
import me.impy.aegis.db.slots.SlotIntegrityException;
|
||||||
|
|
||||||
|
@ -41,15 +39,17 @@ public class SlotCollectionTask<T extends Slot> extends ProgressDialogTask<SlotC
|
||||||
if (slot instanceof PasswordSlot) {
|
if (slot instanceof PasswordSlot) {
|
||||||
char[] password = (char[])params.Obj;
|
char[] password = (char[])params.Obj;
|
||||||
SecretKey key = ((PasswordSlot)slot).deriveKey(password);
|
SecretKey key = ((PasswordSlot)slot).deriveKey(password);
|
||||||
Cipher cipher = Slot.createCipher(key, Cipher.DECRYPT_MODE);
|
Cipher cipher = slot.createDecryptCipher(key);
|
||||||
masterKey = params.Slots.decrypt(slot, cipher);
|
masterKey = slot.getKey(cipher);
|
||||||
} else if (slot instanceof FingerprintSlot) {
|
} else if (slot instanceof FingerprintSlot) {
|
||||||
masterKey = params.Slots.decrypt(slot, (Cipher)params.Obj);
|
masterKey = slot.getKey((Cipher)params.Obj);
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException();
|
throw new RuntimeException();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
} catch (SlotIntegrityException e) { }
|
} catch (SlotIntegrityException e) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (masterKey == null) {
|
if (masterKey == null) {
|
||||||
|
@ -60,7 +60,7 @@ public class SlotCollectionTask<T extends Slot> extends ProgressDialogTask<SlotC
|
||||||
} catch (SlotIntegrityException e) {
|
} catch (SlotIntegrityException e) {
|
||||||
return null;
|
return null;
|
||||||
} catch (SlotException e) {
|
} catch (SlotException e) {
|
||||||
throw new UndeclaredThrowableException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ public class SlotCollectionTask<T extends Slot> extends ProgressDialogTask<SlotC
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Params {
|
public static class Params {
|
||||||
public SlotCollection Slots;
|
public SlotList Slots;
|
||||||
public Object Obj;
|
public Object Obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
45
testdata/aegis_export.json
vendored
45
testdata/aegis_export.json
vendored
File diff suppressed because one or more lines are too long
4
testdata/aegis_export_plain.json
vendored
4
testdata/aegis_export_plain.json
vendored
|
@ -52,7 +52,7 @@
|
||||||
"secret": "6CAIGVYB5MQ6TSZLJ56HJBWU5S3H7FUC",
|
"secret": "6CAIGVYB5MQ6TSZLJ56HJBWU5S3H7FUC",
|
||||||
"algo": "SHA512",
|
"algo": "SHA512",
|
||||||
"digits": 6,
|
"digits": 6,
|
||||||
"counter": 30
|
"counter": 96
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -100,7 +100,7 @@
|
||||||
"secret": "EIQMT7NHFYJUMBKQ35P34JGLG3MO7L2W",
|
"secret": "EIQMT7NHFYJUMBKQ35P34JGLG3MO7L2W",
|
||||||
"algo": "SHA1",
|
"algo": "SHA1",
|
||||||
"digits": 8,
|
"digits": 8,
|
||||||
"counter": 20
|
"counter": 30
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue