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;
|
||||
|
||||
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[] 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.IOException;
|
||||
import java.lang.reflect.UndeclaredThrowableException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
|
@ -20,20 +17,16 @@ import javax.crypto.IllegalBlockSizeException;
|
|||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.spongycastle.crypto.generators.SCrypt;
|
||||
|
||||
public class CryptoUtils {
|
||||
public static final String CRYPTO_HASH = "SHA-256";
|
||||
|
||||
public static final String CRYPTO_CIPHER_RAW = "AES/ECB/NoPadding";
|
||||
public static final byte CRYPTO_KEY_SIZE = 32;
|
||||
|
||||
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 String CRYPTO_AEAD = "AES/GCM/NoPadding";
|
||||
public static final byte CRYPTO_AEAD_KEY_SIZE = 32;
|
||||
public static final byte CRYPTO_AEAD_TAG_SIZE = 16;
|
||||
public static final byte CRYPTO_AEAD_NONCE_SIZE = 12;
|
||||
|
||||
public static final int CRYPTO_SCRYPT_N = 1 << 15;
|
||||
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) {
|
||||
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");
|
||||
}
|
||||
|
||||
public static Cipher createCipher(SecretKey key, int opmode)
|
||||
public static Cipher createEncryptCipher(SecretKey key)
|
||||
throws NoSuchPaddingException, NoSuchAlgorithmException,
|
||||
InvalidAlgorithmParameterException, InvalidKeyException {
|
||||
byte[] nonce = generateNonce();
|
||||
return createCipher(key, opmode, nonce);
|
||||
return createCipher(key, Cipher.ENCRYPT_MODE, null);
|
||||
}
|
||||
|
||||
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,
|
||||
InvalidAlgorithmParameterException, InvalidKeyException {
|
||||
IvParameterSpec spec = new IvParameterSpec(nonce);
|
||||
Cipher cipher = Cipher.getInstance(CRYPTO_CIPHER_AEAD);
|
||||
Cipher cipher = Cipher.getInstance(CRYPTO_AEAD);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
@ -65,8 +71,8 @@ public class CryptoUtils {
|
|||
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);
|
||||
byte[] encrypted = Arrays.copyOfRange(result, 0, result.length - CRYPTO_TAG_SIZE);
|
||||
byte[] tag = Arrays.copyOfRange(result, result.length - CRYPTO_AEAD_TAG_SIZE, result.length);
|
||||
byte[] encrypted = Arrays.copyOfRange(result, 0, result.length - CRYPTO_AEAD_TAG_SIZE);
|
||||
|
||||
return new CryptResult() {{
|
||||
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() {
|
||||
try {
|
||||
KeyGenerator generator = KeyGenerator.getInstance("AES");
|
||||
generator.init(CRYPTO_KEY_SIZE * 8);
|
||||
generator.init(CRYPTO_AEAD_KEY_SIZE * 8);
|
||||
return generator.generateKey();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
|
@ -117,11 +110,7 @@ public class CryptoUtils {
|
|||
}
|
||||
|
||||
public static byte[] generateSalt() {
|
||||
return generateRandomBytes(CRYPTO_KEY_SIZE);
|
||||
}
|
||||
|
||||
public static byte[] generateNonce() {
|
||||
return generateRandomBytes(CRYPTO_NONCE_SIZE);
|
||||
return generateRandomBytes(CRYPTO_AEAD_KEY_SIZE);
|
||||
}
|
||||
|
||||
public static byte[] generateRandomBytes(int length) {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package me.impy.aegis.crypto;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Build;
|
||||
import android.security.keystore.KeyGenParameterSpec;
|
||||
import android.security.keystore.KeyPermanentlyInvalidatedException;
|
||||
|
@ -51,11 +50,11 @@ public class KeyStoreHandle {
|
|||
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)
|
||||
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
|
||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
||||
.setUserAuthenticationRequired(true)
|
||||
.setRandomizedEncryptionRequired(false)
|
||||
.setKeySize(CryptoUtils.CRYPTO_KEY_SIZE * 8)
|
||||
.setRandomizedEncryptionRequired(true)
|
||||
.setKeySize(CryptoUtils.CRYPTO_AEAD_KEY_SIZE * 8)
|
||||
.build());
|
||||
|
||||
return generator.generateKey();
|
||||
|
@ -81,8 +80,7 @@ public class KeyStoreHandle {
|
|||
// and see if KeyPermanentlyInvalidatedException is thrown
|
||||
if (isSupported()) {
|
||||
try {
|
||||
@SuppressLint("GetInstance")
|
||||
Cipher cipher = Cipher.getInstance(CryptoUtils.CRYPTO_CIPHER_RAW);
|
||||
Cipher cipher = Cipher.getInstance(CryptoUtils.CRYPTO_AEAD);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key);
|
||||
} catch (KeyPermanentlyInvalidatedException e) {
|
||||
return null;
|
||||
|
|
|
@ -28,7 +28,7 @@ public class MasterKey implements Serializable {
|
|||
|
||||
public CryptResult encrypt(byte[] bytes) throws MasterKeyException {
|
||||
try {
|
||||
Cipher cipher = CryptoUtils.createCipher(_key, Cipher.ENCRYPT_MODE);
|
||||
Cipher cipher = CryptoUtils.createEncryptCipher(_key);
|
||||
return CryptoUtils.encrypt(bytes, cipher);
|
||||
} catch (NoSuchPaddingException
|
||||
| NoSuchAlgorithmException
|
||||
|
@ -42,7 +42,7 @@ public class MasterKey implements Serializable {
|
|||
|
||||
public CryptResult decrypt(byte[] bytes, CryptParameters params) throws MasterKeyException {
|
||||
try {
|
||||
Cipher cipher = CryptoUtils.createCipher(_key, Cipher.DECRYPT_MODE, params.Nonce);
|
||||
Cipher cipher = CryptoUtils.createDecryptCipher(_key, params.Nonce);
|
||||
return CryptoUtils.decrypt(bytes, cipher, params);
|
||||
} catch (NoSuchPaddingException
|
||||
| NoSuchAlgorithmException
|
||||
|
@ -55,10 +55,6 @@ public class MasterKey implements Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
public byte[] getHash() {
|
||||
return CryptoUtils.hashKey(_key);
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return _key.getEncoded();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package me.impy.aegis.db;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
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.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.db.slots.SlotList;
|
||||
import me.impy.aegis.db.slots.SlotListException;
|
||||
import me.impy.aegis.encoding.Base64;
|
||||
import me.impy.aegis.encoding.Base64Exception;
|
||||
import me.impy.aegis.encoding.Hex;
|
||||
import me.impy.aegis.encoding.HexException;
|
||||
|
||||
public class DatabaseFile {
|
||||
|
@ -21,22 +21,15 @@ public class DatabaseFile {
|
|||
|
||||
private Object _content;
|
||||
private CryptParameters _cryptParameters;
|
||||
private SlotCollection _slots;
|
||||
private SlotList _slots;
|
||||
|
||||
public byte[] serialize() {
|
||||
try {
|
||||
JSONObject cryptObj = null;
|
||||
if (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;
|
||||
// don't write the crypt parameters and slots if the content is not encrypted
|
||||
boolean plain = _content instanceof JSONObject || !isEncrypted();
|
||||
JSONObject headerObj = new JSONObject();
|
||||
headerObj.put("slots", plain ? JSONObject.NULL : SlotCollection.serialize(_slots));
|
||||
headerObj.put("params", plain ? JSONObject.NULL : cryptObj);
|
||||
headerObj.put("slots", plain ? JSONObject.NULL : SlotList.serialize(_slots));
|
||||
headerObj.put("params", plain ? JSONObject.NULL : _cryptParameters.toJson());
|
||||
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("version", VERSION);
|
||||
|
@ -58,17 +51,14 @@ public class DatabaseFile {
|
|||
throw new DatabaseFileException("unsupported version");
|
||||
}
|
||||
|
||||
JSONObject slotObj = headerObj.optJSONObject("slots");
|
||||
JSONArray slotObj = headerObj.optJSONArray("slots");
|
||||
if (slotObj != null) {
|
||||
_slots = SlotCollection.deserialize(slotObj);
|
||||
_slots = SlotList.deserialize(slotObj);
|
||||
}
|
||||
|
||||
JSONObject cryptObj = headerObj.optJSONObject("params");
|
||||
if (cryptObj != null) {
|
||||
_cryptParameters = new CryptParameters() {{
|
||||
Nonce = Hex.decode(cryptObj.getString("nonce"));
|
||||
Tag = Hex.decode(cryptObj.getString("tag"));
|
||||
}};
|
||||
_cryptParameters = CryptParameters.parseJson(cryptObj);
|
||||
}
|
||||
|
||||
if (cryptObj == null || slotObj == null) {
|
||||
|
@ -76,7 +66,7 @@ public class DatabaseFile {
|
|||
} else {
|
||||
_content = obj.getString("db");
|
||||
}
|
||||
} catch (SlotCollectionException | UnsupportedEncodingException | JSONException | HexException e) {
|
||||
} catch (SlotListException | UnsupportedEncodingException | JSONException | HexException e) {
|
||||
throw new DatabaseFileException(e);
|
||||
}
|
||||
}
|
||||
|
@ -118,11 +108,11 @@ public class DatabaseFile {
|
|||
}
|
||||
}
|
||||
|
||||
public SlotCollection getSlots() {
|
||||
public SlotList getSlots() {
|
||||
return _slots;
|
||||
}
|
||||
|
||||
public void setSlots(SlotCollection slots) {
|
||||
public void setSlots(SlotList slots) {
|
||||
_slots = slots;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ import java.util.List;
|
|||
|
||||
import me.impy.aegis.BuildConfig;
|
||||
import me.impy.aegis.crypto.MasterKey;
|
||||
import me.impy.aegis.db.slots.SlotCollection;
|
||||
import me.impy.aegis.db.slots.SlotList;
|
||||
|
||||
public class DatabaseManager {
|
||||
private static final String FILENAME = "aegis.json";
|
||||
|
@ -196,7 +196,7 @@ public class DatabaseManager {
|
|||
return _file;
|
||||
}
|
||||
|
||||
public void enableEncryption(MasterKey key, SlotCollection slots) {
|
||||
public void enableEncryption(MasterKey key, SlotList slots) {
|
||||
assertState(false, true);
|
||||
_key = key;
|
||||
_file.setSlots(slots);
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
package me.impy.aegis.db.slots;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.crypto.AEADBadTagException;
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
|
@ -17,6 +18,8 @@ import javax.crypto.NoSuchPaddingException;
|
|||
import javax.crypto.SecretKey;
|
||||
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.MasterKey;
|
||||
import me.impy.aegis.encoding.Hex;
|
||||
|
@ -29,40 +32,55 @@ public abstract class Slot implements Serializable {
|
|||
|
||||
protected UUID _uuid;
|
||||
protected byte[] _encryptedMasterKey;
|
||||
protected CryptParameters _encryptedMasterKeyParams;
|
||||
|
||||
protected Slot() {
|
||||
_uuid = UUID.randomUUID();
|
||||
}
|
||||
|
||||
// getKey decrypts the encrypted master key in this slot with the given key and returns it.
|
||||
public SecretKey getKey(Cipher cipher) throws SlotException {
|
||||
// getKey decrypts the encrypted master key in this slot using the given cipher and returns it.
|
||||
public MasterKey getKey(Cipher cipher) throws SlotException, SlotIntegrityException {
|
||||
try {
|
||||
byte[] decryptedKeyBytes = cipher.doFinal(_encryptedMasterKey);
|
||||
return new SecretKeySpec(decryptedKeyBytes, CryptoUtils.CRYPTO_CIPHER_AEAD);
|
||||
} catch (BadPaddingException | IllegalBlockSizeException e) {
|
||||
CryptResult res = CryptoUtils.decrypt(_encryptedMasterKey, cipher, _encryptedMasterKeyParams);
|
||||
SecretKey key = new SecretKeySpec(res.Data, CryptoUtils.CRYPTO_AEAD);
|
||||
return new MasterKey(key);
|
||||
} catch (AEADBadTagException e) {
|
||||
throw new SlotIntegrityException(e);
|
||||
} catch (IOException | BadPaddingException | IllegalBlockSizeException e) {
|
||||
throw new SlotException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// setKey encrypts the given master key with the given key and stores the result in this slot.
|
||||
// setKey encrypts the given master key using the given cipher and stores the result in this slot.
|
||||
public void setKey(MasterKey masterKey, Cipher cipher) throws SlotException {
|
||||
try {
|
||||
byte[] masterKeyBytes = masterKey.getBytes();
|
||||
_encryptedMasterKey = cipher.doFinal(masterKeyBytes);
|
||||
CryptResult res = CryptoUtils.encrypt(masterKeyBytes, cipher);
|
||||
_encryptedMasterKey = res.Data;
|
||||
_encryptedMasterKeyParams = res.Parameters;
|
||||
} 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 SlotException {
|
||||
public static Cipher createEncryptCipher(SecretKey key) throws SlotException {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance(CryptoUtils.CRYPTO_CIPHER_RAW);
|
||||
cipher.init(mode, key);
|
||||
return cipher;
|
||||
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) {
|
||||
return CryptoUtils.createEncryptCipher(key);
|
||||
} catch (InvalidAlgorithmParameterException
|
||||
| NoSuchPaddingException
|
||||
| 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);
|
||||
}
|
||||
}
|
||||
|
@ -70,9 +88,11 @@ public abstract class Slot implements Serializable {
|
|||
public JSONObject serialize() {
|
||||
try {
|
||||
JSONObject obj = new JSONObject();
|
||||
JSONObject paramObj = _encryptedMasterKeyParams.toJson();
|
||||
obj.put("type", getType());
|
||||
obj.put("uuid", _uuid.toString());
|
||||
obj.put("key", Hex.encode(_encryptedMasterKey));
|
||||
obj.put("key_params", paramObj);
|
||||
return obj;
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
|
@ -84,13 +104,17 @@ public abstract class Slot implements Serializable {
|
|||
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"));
|
||||
}
|
||||
|
||||
JSONObject paramObj = obj.getJSONObject("key_params");
|
||||
_encryptedMasterKey = Hex.decode(obj.getString("key"));
|
||||
_encryptedMasterKeyParams = CryptParameters.parseJson(paramObj);
|
||||
} catch (JSONException | HexException 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;
|
||||
|
||||
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.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
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 {
|
||||
public class SlotList implements Iterable<Slot>, Serializable {
|
||||
private List<Slot> _slots = new ArrayList<>();
|
||||
private byte[] _masterHash;
|
||||
|
||||
public static JSONObject serialize(SlotCollection slots) {
|
||||
try {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("hash", Hex.encode(slots.getMasterHash()));
|
||||
|
||||
JSONArray entries = new JSONArray();
|
||||
public static JSONArray serialize(SlotList slots) {
|
||||
JSONArray array = new JSONArray();
|
||||
for (Slot slot : slots) {
|
||||
entries.put(slot.serialize());
|
||||
array.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 {
|
||||
SlotCollection slots = new SlotCollection();
|
||||
public static SlotList deserialize(JSONArray array) throws SlotListException {
|
||||
SlotList slots = new SlotList();
|
||||
|
||||
try {
|
||||
byte[] masterHash = Hex.decode(obj.getString("hash"));
|
||||
slots.setMasterHash(masterHash);
|
||||
|
||||
JSONArray entries = obj.getJSONArray("entries");
|
||||
for (int i = 0; i < entries.length(); i++) {
|
||||
for (int i = 0; i < array.length(); i++) {
|
||||
Slot slot;
|
||||
JSONObject slotObj = entries.getJSONObject(i);
|
||||
JSONObject slotObj = array.getJSONObject(i);
|
||||
|
||||
switch (slotObj.getInt("type")) {
|
||||
case Slot.TYPE_RAW:
|
||||
|
@ -65,8 +46,8 @@ public class SlotCollection implements Iterable<Slot>, Serializable {
|
|||
slot.deserialize(slotObj);
|
||||
slots.add(slot);
|
||||
}
|
||||
} catch (SlotException | JSONException | HexException e) {
|
||||
throw new SlotCollectionException(e);
|
||||
} catch (SlotException | JSONException e) {
|
||||
throw new SlotListException(e);
|
||||
}
|
||||
|
||||
return slots;
|
||||
|
@ -116,26 +97,4 @@ public class SlotCollection implements Iterable<Slot>, Serializable {
|
|||
public Iterator<Slot> 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.PasswordSlot;
|
||||
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.helpers.FingerprintHelper;
|
||||
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 {
|
||||
private EditText _textPassword;
|
||||
|
||||
private SlotCollection _slots;
|
||||
private SlotList _slots;
|
||||
private FingerprintUiHelper _fingerHelper;
|
||||
private Cipher _fingerCipher;
|
||||
|
||||
|
@ -57,7 +57,7 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
|
|||
}
|
||||
|
||||
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
|
||||
FingerprintManager manager = FingerprintHelper.getManager(this);
|
||||
|
@ -75,7 +75,7 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
|
|||
invalidated = true;
|
||||
continue;
|
||||
}
|
||||
_fingerCipher = Slot.createCipher(key, Cipher.DECRYPT_MODE);
|
||||
_fingerCipher = slot.createDecryptCipher(key);
|
||||
_fingerHelper = new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, this);
|
||||
boxFingerprint.setVisibility(View.VISIBLE);
|
||||
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.PasswordSlot;
|
||||
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.DatabaseFile;
|
||||
import me.impy.aegis.db.DatabaseManager;
|
||||
|
@ -145,7 +145,7 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback {
|
|||
masterKey = MasterKey.generate();
|
||||
}
|
||||
|
||||
SlotCollection slots = null;
|
||||
SlotList slots = null;
|
||||
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
|
||||
|
@ -153,8 +153,8 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback {
|
|||
throw new RuntimeException();
|
||||
}
|
||||
try {
|
||||
slots = new SlotCollection();
|
||||
slots.encrypt(_passwordSlot, masterKey, _passwordCipher);
|
||||
_passwordSlot.setKey(masterKey, _passwordCipher);
|
||||
slots = new SlotList();
|
||||
slots.add(_passwordSlot);
|
||||
_databaseFile.setSlots(slots);
|
||||
} catch (SlotException e) {
|
||||
|
@ -168,7 +168,7 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback {
|
|||
// and add it to the list of slots
|
||||
FingerprintSlot slot = _authenticatedSlide.getFingerSlot();
|
||||
Cipher cipher = _authenticatedSlide.getFingerCipher();
|
||||
slots.encrypt(slot, masterKey, cipher);
|
||||
slot.setKey(masterKey, cipher);
|
||||
slots.add(slot);
|
||||
} catch (SlotException e) {
|
||||
setException(e);
|
||||
|
@ -204,7 +204,7 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback {
|
|||
public void onTaskFinished(SecretKey key) {
|
||||
if (key != null) {
|
||||
try {
|
||||
_passwordCipher = Slot.createCipher(key, Cipher.ENCRYPT_MODE);
|
||||
_passwordCipher = Slot.createEncryptCipher(key);
|
||||
} catch (SlotException e) {
|
||||
setException(e);
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ import me.impy.aegis.db.DatabaseEntry;
|
|||
import me.impy.aegis.db.DatabaseManager;
|
||||
import me.impy.aegis.db.DatabaseManagerException;
|
||||
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.helpers.PermissionHelper;
|
||||
import me.impy.aegis.importers.AegisImporter;
|
||||
|
@ -369,7 +369,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
|
|||
return;
|
||||
}
|
||||
|
||||
SlotCollection slots = (SlotCollection) data.getSerializableExtra("slots");
|
||||
SlotList slots = (SlotList) data.getSerializableExtra("slots");
|
||||
_db.getFile().setSlots(slots);
|
||||
saveDatabase();
|
||||
}
|
||||
|
@ -390,9 +390,9 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
|
|||
public void onSlotResult(Slot slot, Cipher cipher) {
|
||||
MasterKey masterKey = MasterKey.generate();
|
||||
|
||||
SlotCollection slots = new SlotCollection();
|
||||
SlotList slots = new SlotList();
|
||||
try {
|
||||
slots.encrypt(slot, masterKey, cipher);
|
||||
slot.setKey(masterKey, cipher);
|
||||
} catch (SlotException e) {
|
||||
onException(e);
|
||||
return;
|
||||
|
|
|
@ -20,7 +20,7 @@ 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.SlotList;
|
||||
import me.impy.aegis.db.slots.SlotException;
|
||||
import me.impy.aegis.helpers.FingerprintHelper;
|
||||
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 {
|
||||
private MasterKey _masterKey;
|
||||
private SlotCollection _slots;
|
||||
private SlotList _slots;
|
||||
private SlotAdapter _adapter;
|
||||
|
||||
private boolean _edited = false;
|
||||
|
@ -65,7 +65,7 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
|
|||
|
||||
// load the slots and masterKey
|
||||
_masterKey = (MasterKey) getIntent().getSerializableExtra("masterKey");
|
||||
_slots = (SlotCollection) getIntent().getSerializableExtra("slots");
|
||||
_slots = (SlotList) getIntent().getSerializableExtra("slots");
|
||||
for (Slot slot : _slots) {
|
||||
_adapter.addSlot(slot);
|
||||
}
|
||||
|
@ -176,7 +176,7 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
|
|||
@Override
|
||||
public void onSlotResult(Slot slot, Cipher cipher) {
|
||||
try {
|
||||
_slots.encrypt(slot, _masterKey, cipher);
|
||||
slot.setKey(_masterKey, cipher);
|
||||
} catch (SlotException e) {
|
||||
onException(e);
|
||||
return;
|
||||
|
|
|
@ -38,7 +38,7 @@ public class FingerprintDialogFragment extends SlotDialogFragment implements Fin
|
|||
try {
|
||||
_slot = new FingerprintSlot();
|
||||
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);
|
||||
} catch (KeyStoreHandleException | SlotException e) {
|
||||
throw new RuntimeException(e);
|
||||
|
|
|
@ -51,7 +51,7 @@ public class PasswordDialogFragment extends SlotDialogFragment {
|
|||
DerivationTask task = new DerivationTask(getActivity(), key -> {
|
||||
Cipher cipher;
|
||||
try {
|
||||
cipher = Slot.createCipher(key, Cipher.ENCRYPT_MODE);
|
||||
cipher = Slot.createEncryptCipher(key);
|
||||
} catch (SlotException e) {
|
||||
getListener().onException(e);
|
||||
dialog.cancel();
|
||||
|
|
|
@ -113,7 +113,7 @@ public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiH
|
|||
}
|
||||
|
||||
try {
|
||||
_fingerCipher = Slot.createCipher(key, Cipher.ENCRYPT_MODE);
|
||||
_fingerCipher = Slot.createEncryptCipher(key);
|
||||
} catch (Exception e) {
|
||||
throw new UndeclaredThrowableException(e);
|
||||
}
|
||||
|
|
|
@ -2,8 +2,6 @@ package me.impy.aegis.ui.tasks;
|
|||
|
||||
import android.content.Context;
|
||||
|
||||
import java.lang.reflect.UndeclaredThrowableException;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
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.PasswordSlot;
|
||||
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.SlotIntegrityException;
|
||||
|
||||
|
@ -41,15 +39,17 @@ public class SlotCollectionTask<T extends Slot> extends ProgressDialogTask<SlotC
|
|||
if (slot instanceof PasswordSlot) {
|
||||
char[] password = (char[])params.Obj;
|
||||
SecretKey key = ((PasswordSlot)slot).deriveKey(password);
|
||||
Cipher cipher = Slot.createCipher(key, Cipher.DECRYPT_MODE);
|
||||
masterKey = params.Slots.decrypt(slot, cipher);
|
||||
Cipher cipher = slot.createDecryptCipher(key);
|
||||
masterKey = slot.getKey(cipher);
|
||||
} else if (slot instanceof FingerprintSlot) {
|
||||
masterKey = params.Slots.decrypt(slot, (Cipher)params.Obj);
|
||||
masterKey = slot.getKey((Cipher)params.Obj);
|
||||
} else {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
break;
|
||||
} catch (SlotIntegrityException e) { }
|
||||
} catch (SlotIntegrityException e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (masterKey == null) {
|
||||
|
@ -60,7 +60,7 @@ public class SlotCollectionTask<T extends Slot> extends ProgressDialogTask<SlotC
|
|||
} catch (SlotIntegrityException e) {
|
||||
return null;
|
||||
} 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 SlotCollection Slots;
|
||||
public SlotList Slots;
|
||||
public Object Obj;
|
||||
}
|
||||
|
||||
|
|
31
testdata/aegis_export.json
vendored
31
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",
|
||||
"algo": "SHA512",
|
||||
"digits": 6,
|
||||
"counter": 30
|
||||
"counter": 96
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -100,7 +100,7 @@
|
|||
"secret": "EIQMT7NHFYJUMBKQ35P34JGLG3MO7L2W",
|
||||
"algo": "SHA1",
|
||||
"digits": 8,
|
||||
"counter": 20
|
||||
"counter": 30
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue