mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-04-24 07:46:07 +00:00
Match slot ID's to keystore aliases
This commit is contained in:
parent
c24b691a26
commit
576f908e01
13 changed files with 143 additions and 51 deletions
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
|
@ -24,7 +24,7 @@
|
|||
</value>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="JDK" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
|
|
@ -61,12 +61,17 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
|
|||
FingerprintManager manager = FingerprintHelper.getManager(this);
|
||||
if (manager != null && _slots.has(FingerprintSlot.class)) {
|
||||
try {
|
||||
KeyStoreHandle handle = new KeyStoreHandle();
|
||||
if (handle.keyExists()) {
|
||||
SecretKey key = handle.getKey();
|
||||
_fingerCipher = Slot.createCipher(key, Cipher.DECRYPT_MODE);
|
||||
_fingerHelper = new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, this);
|
||||
boxFingerprint.setVisibility(View.VISIBLE);
|
||||
// find a fingerprint slot with an id that matches an alias in the keystore
|
||||
for (FingerprintSlot slot : _slots.findAll(FingerprintSlot.class)) {
|
||||
String id = slot.getID();
|
||||
KeyStoreHandle handle = new KeyStoreHandle();
|
||||
if (handle.containsKey(id)) {
|
||||
SecretKey key = handle.getKey(id);
|
||||
_fingerCipher = Slot.createCipher(key, Cipher.DECRYPT_MODE);
|
||||
_fingerHelper = new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, this);
|
||||
boxFingerprint.setVisibility(View.VISIBLE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new UndeclaredThrowableException(e);
|
||||
|
|
|
@ -24,6 +24,7 @@ import javax.crypto.Cipher;
|
|||
import javax.crypto.SecretKey;
|
||||
|
||||
import me.impy.aegis.crypto.KeyStoreHandle;
|
||||
import me.impy.aegis.crypto.slots.FingerprintSlot;
|
||||
import me.impy.aegis.crypto.slots.Slot;
|
||||
import me.impy.aegis.helpers.FingerprintUiHelper;
|
||||
import me.impy.aegis.helpers.AuthHelper;
|
||||
|
@ -39,6 +40,7 @@ public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiH
|
|||
private TextView _textFingerprint;
|
||||
private FingerprintUiHelper _fingerHelper;
|
||||
private KeyStoreHandle _storeHandle;
|
||||
private FingerprintSlot _slot;
|
||||
private Cipher _fingerCipher;
|
||||
private boolean _fingerAuthenticated;
|
||||
|
||||
|
@ -93,13 +95,9 @@ public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiH
|
|||
try {
|
||||
if (_storeHandle == null) {
|
||||
_storeHandle = new KeyStoreHandle();
|
||||
_slot = new FingerprintSlot();
|
||||
}
|
||||
// TODO: consider regenerating the key here if it already exists
|
||||
if (!_storeHandle.keyExists()) {
|
||||
key = _storeHandle.generateKey(true);
|
||||
} else {
|
||||
key = _storeHandle.getKey();
|
||||
}
|
||||
key = _storeHandle.generateKey(_slot.getID());
|
||||
} catch (Exception e) {
|
||||
throw new UndeclaredThrowableException(e);
|
||||
}
|
||||
|
@ -160,7 +158,7 @@ public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiH
|
|||
|
||||
View view = getView();
|
||||
if (view != null) {
|
||||
Snackbar snackbar = Snackbar.make(getView(), message, Snackbar.LENGTH_LONG);
|
||||
Snackbar snackbar = Snackbar.make(view, message, Snackbar.LENGTH_LONG);
|
||||
snackbar.show();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import me.impy.aegis.helpers.FingerprintUiHelper;
|
|||
public class FingerprintDialogFragment extends SlotDialogFragment implements FingerprintUiHelper.Callback {
|
||||
private Cipher _cipher;
|
||||
private FingerprintUiHelper _helper;
|
||||
private FingerprintSlot _slot;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
|
@ -32,8 +33,8 @@ public class FingerprintDialogFragment extends SlotDialogFragment implements Fin
|
|||
|
||||
FingerprintManager manager = FingerprintHelper.getManager(getContext());
|
||||
try {
|
||||
KeyStoreHandle handle = new KeyStoreHandle();
|
||||
SecretKey key = handle.getKey();
|
||||
_slot = new FingerprintSlot();
|
||||
SecretKey key = new KeyStoreHandle().generateKey(_slot.getID());
|
||||
_cipher = Slot.createCipher(key, Cipher.ENCRYPT_MODE);
|
||||
_helper = new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, this);
|
||||
} catch (Exception e) {
|
||||
|
@ -67,8 +68,7 @@ public class FingerprintDialogFragment extends SlotDialogFragment implements Fin
|
|||
|
||||
@Override
|
||||
public void onAuthenticated() {
|
||||
FingerprintSlot slot = new FingerprintSlot();
|
||||
getListener().onSlotResult(slot, _cipher);
|
||||
getListener().onSlotResult(_slot, _cipher);
|
||||
dismiss();
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,9 @@ import android.widget.Toast;
|
|||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
import me.impy.aegis.crypto.KeyStoreHandle;
|
||||
import me.impy.aegis.crypto.MasterKey;
|
||||
import me.impy.aegis.crypto.slots.FingerprintSlot;
|
||||
import me.impy.aegis.crypto.slots.PasswordSlot;
|
||||
import me.impy.aegis.crypto.slots.Slot;
|
||||
import me.impy.aegis.crypto.slots.SlotCollection;
|
||||
|
@ -36,16 +38,10 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
|
|||
bar.setHomeAsUpIndicator(R.drawable.ic_close);
|
||||
bar.setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
// only show the fingerprint option if we can get an instance of the fingerprint manager
|
||||
// TODO: also hide the option if this device's fingerprint has already been registered
|
||||
if (FingerprintHelper.getManager(this) != null) {
|
||||
findViewById(R.id.button_add_fingerprint).setOnClickListener(view -> {
|
||||
FingerprintDialogFragment dialog = new FingerprintDialogFragment();
|
||||
dialog.show(getSupportFragmentManager(), null);
|
||||
});
|
||||
} else {
|
||||
findViewById(R.id.button_add_fingerprint).setVisibility(View.GONE);
|
||||
}
|
||||
findViewById(R.id.button_add_fingerprint).setOnClickListener(view -> {
|
||||
FingerprintDialogFragment dialog = new FingerprintDialogFragment();
|
||||
dialog.show(getSupportFragmentManager(), null);
|
||||
});
|
||||
|
||||
findViewById(R.id.button_add_password).setOnClickListener(view -> {
|
||||
PasswordDialogFragment dialog = new PasswordDialogFragment();
|
||||
|
@ -66,6 +62,26 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
|
|||
for (Slot slot : _slots) {
|
||||
_adapter.addSlot(slot);
|
||||
}
|
||||
|
||||
updateFingerprintButton();
|
||||
}
|
||||
|
||||
private void updateFingerprintButton() {
|
||||
// only show the fingerprint option if we can get an instance of the fingerprint manager
|
||||
// and if none of the slots in the collection has a matching alias in the keystore
|
||||
int visibility = View.VISIBLE;
|
||||
try {
|
||||
KeyStoreHandle keyStore = new KeyStoreHandle();
|
||||
for (FingerprintSlot slot : _slots.findAll(FingerprintSlot.class)) {
|
||||
if (keyStore.containsKey(slot.getID()) && FingerprintHelper.getManager(this) != null) {
|
||||
visibility = View.GONE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
visibility = View.GONE;
|
||||
}
|
||||
findViewById(R.id.button_add_fingerprint).setVisibility(visibility);
|
||||
}
|
||||
|
||||
private boolean onSave() {
|
||||
|
@ -132,6 +148,7 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
|
|||
.setPositiveButton(android.R.string.yes, (dialog, whichButton) -> {
|
||||
_slots.remove(slot);
|
||||
_adapter.removeSlot(slot);
|
||||
updateFingerprintButton();
|
||||
})
|
||||
.setNegativeButton(android.R.string.no, null)
|
||||
.show();
|
||||
|
@ -145,8 +162,10 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
|
|||
onException(e);
|
||||
return;
|
||||
}
|
||||
|
||||
_slots.add(slot);
|
||||
_adapter.addSlot(slot);
|
||||
updateFingerprintButton();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -120,7 +120,7 @@ public class CryptoUtils {
|
|||
return generateRandomBytes(CRYPTO_NONCE_SIZE);
|
||||
}
|
||||
|
||||
private static byte[] generateRandomBytes(int length) {
|
||||
public static byte[] generateRandomBytes(int length) {
|
||||
SecureRandom random = new SecureRandom();
|
||||
byte[] data = new byte[length];
|
||||
random.nextBytes(data);
|
||||
|
|
|
@ -14,9 +14,10 @@ import java.security.cert.CertificateException;
|
|||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import me.impy.aegis.crypto.slots.FingerprintSlot;
|
||||
|
||||
public class KeyStoreHandle {
|
||||
private final KeyStore _keyStore;
|
||||
private static final String KEY_NAME = "AegisKey";
|
||||
private static final String STORE_NAME = "AndroidKeyStore";
|
||||
|
||||
public KeyStoreHandle() throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
|
||||
|
@ -24,18 +25,18 @@ public class KeyStoreHandle {
|
|||
_keyStore.load(null);
|
||||
}
|
||||
|
||||
public boolean keyExists() throws KeyStoreException {
|
||||
return _keyStore.containsAlias(KEY_NAME);
|
||||
public boolean containsKey(String id) throws KeyStoreException {
|
||||
return _keyStore.containsAlias(id);
|
||||
}
|
||||
|
||||
public SecretKey generateKey(boolean authRequired) throws Exception {
|
||||
public SecretKey generateKey(String id) throws Exception {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, STORE_NAME);
|
||||
generator.init(new KeyGenParameterSpec.Builder(KEY_NAME,
|
||||
generator.init(new KeyGenParameterSpec.Builder(id,
|
||||
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
|
||||
.setBlockModes(KeyProperties.BLOCK_MODE_ECB)
|
||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
||||
.setUserAuthenticationRequired(authRequired)
|
||||
.setUserAuthenticationRequired(true)
|
||||
.setRandomizedEncryptionRequired(false)
|
||||
.setKeySize(CryptoUtils.CRYPTO_KEY_SIZE * 8)
|
||||
.build());
|
||||
|
@ -46,7 +47,7 @@ public class KeyStoreHandle {
|
|||
}
|
||||
}
|
||||
|
||||
public SecretKey getKey() throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException {
|
||||
return (SecretKey) _keyStore.getKey(KEY_NAME, null);
|
||||
public SecretKey getKey(String id) throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException {
|
||||
return (SecretKey) _keyStore.getKey(id, null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ public class PasswordSlot extends RawSlot {
|
|||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return 1 + CryptoUtils.CRYPTO_KEY_SIZE + /* _n, _r, _p */ 4 + 4 + 4 + CryptoUtils.CRYPTO_SALT_SIZE;
|
||||
return super.getSize() + /* _n, _r, _p */ 4 + 4 + 4 + CryptoUtils.CRYPTO_SALT_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -13,6 +13,7 @@ public class RawSlot extends Slot {
|
|||
public byte[] serialize() {
|
||||
LittleByteBuffer buffer = LittleByteBuffer.allocate(getSize());
|
||||
buffer.put(getType());
|
||||
buffer.put(_id);
|
||||
buffer.put(_encryptedMasterKey);
|
||||
return buffer.array();
|
||||
}
|
||||
|
@ -23,13 +24,15 @@ public class RawSlot extends Slot {
|
|||
if (buffer.get() != getType()) {
|
||||
throw new Exception("slot type mismatch");
|
||||
}
|
||||
_id = new byte[ID_SIZE];
|
||||
buffer.get(_id);
|
||||
_encryptedMasterKey = new byte[CryptoUtils.CRYPTO_KEY_SIZE];
|
||||
buffer.get(_encryptedMasterKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return 1 + CryptoUtils.CRYPTO_KEY_SIZE;
|
||||
return 1 + ID_SIZE + CryptoUtils.CRYPTO_KEY_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -15,14 +15,21 @@ import javax.crypto.spec.SecretKeySpec;
|
|||
|
||||
import me.impy.aegis.crypto.CryptoUtils;
|
||||
import me.impy.aegis.crypto.MasterKey;
|
||||
import me.impy.aegis.util.Hex;
|
||||
|
||||
public abstract class Slot implements Serializable {
|
||||
public final static byte TYPE_RAW = 0x00;
|
||||
public final static byte TYPE_DERIVED = 0x01;
|
||||
public final static byte TYPE_FINGERPRINT = 0x02;
|
||||
public final static int ID_SIZE = 16;
|
||||
|
||||
protected byte[] _id;
|
||||
protected byte[] _encryptedMasterKey;
|
||||
|
||||
protected Slot() {
|
||||
_id = CryptoUtils.generateRandomBytes(ID_SIZE);
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
@ -38,7 +45,7 @@ public abstract class Slot implements Serializable {
|
|||
CryptoUtils.zero(masterKeyBytes);
|
||||
}
|
||||
|
||||
// suppressing the AES ECB warning
|
||||
// suppress the AES ECB warning
|
||||
// this is perfectly safe because we discard this cipher after passing CryptoUtils.CRYPTO_KEY_SIZE bytes through it
|
||||
@SuppressLint("getInstance")
|
||||
public static Cipher createCipher(SecretKey key, int mode) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException {
|
||||
|
@ -47,6 +54,10 @@ public abstract class Slot implements Serializable {
|
|||
return cipher;
|
||||
}
|
||||
|
||||
public String getID() {
|
||||
return Hex.toString(_id);
|
||||
}
|
||||
|
||||
public abstract int getSize();
|
||||
public abstract byte getType();
|
||||
|
||||
|
|
46
app/src/main/java/me/impy/aegis/util/Hex.java
Normal file
46
app/src/main/java/me/impy/aegis/util/Hex.java
Normal file
|
@ -0,0 +1,46 @@
|
|||
package me.impy.aegis.util;
|
||||
|
||||
// The hexadecimal utility functions in this file were taken and modified from: http://www.docjar.com/html/api/com/sun/xml/internal/bind/DatatypeConverterImpl.java.html
|
||||
// It is licensed under GPLv2 with a classpath exception.
|
||||
public class Hex {
|
||||
private Hex() {
|
||||
}
|
||||
|
||||
private static int hexToBin(char ch) {
|
||||
if ('0' <= ch && ch <= '9') return ch - '0';
|
||||
if ('A' <= ch && ch <= 'F') return ch - 'A' + 10;
|
||||
if ('a' <= ch && ch <= 'f') return ch - 'a' + 10;
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static final char[] hexCode = "0123456789abcdef".toCharArray();
|
||||
|
||||
public static byte[] toBytes(String s) {
|
||||
final int len = s.length();
|
||||
|
||||
if (len % 2 != 0)
|
||||
throw new IllegalArgumentException("hexBinary needs to be even-length: " + s);
|
||||
|
||||
byte[] out = new byte[len / 2];
|
||||
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
int h = hexToBin(s.charAt(i));
|
||||
int l = hexToBin(s.charAt(i + 1));
|
||||
if (h == -1 || l == -1)
|
||||
throw new IllegalArgumentException("contains illegal character for hexBinary: " + s);
|
||||
|
||||
out[i / 2] = (byte) (h * 16 + l);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
public static String toString(byte[] data) {
|
||||
StringBuilder r = new StringBuilder(data.length * 2);
|
||||
for (byte b : data) {
|
||||
r.append(hexCode[(b >> 4) & 0xF]);
|
||||
r.append(hexCode[(b & 0xF)]);
|
||||
}
|
||||
return r.toString();
|
||||
}
|
||||
}
|
|
@ -24,20 +24,28 @@
|
|||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="15dp"/>
|
||||
|
||||
<TextView
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:text="Slot name"
|
||||
android:id="@+id/text_slot_name"
|
||||
android:textColor="@color/primary_text"
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"/>
|
||||
android:layout_weight="1">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_slot_used"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
</TextView>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:text="Slot name"
|
||||
android:id="@+id/text_slot_name"
|
||||
android:textColor="@color/primary_text"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_slot_used"
|
||||
android:text="(this device)"
|
||||
android:visibility="gone"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
|
|
@ -70,6 +70,7 @@ A slot has the following structure.
|
|||
| Length | Contents |
|
||||
|:-------|:--------------------|
|
||||
| `1` | `uint8_t` Type |
|
||||
| `16` | ID |
|
||||
| `32` | Encrypted key |
|
||||
| `?` | Additional data |
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue