Match slot ID's to keystore aliases

pull/41/head
Alexander Bakker 7 years ago
parent c24b691a26
commit 576f908e01

@ -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();

@ -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…
Cancel
Save