From 576f908e0181f89e6361bf69c8b6d099e4a1de67 Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Sat, 10 Feb 2018 17:20:41 +0100 Subject: [PATCH] Match slot ID's to keystore aliases --- .idea/misc.xml | 2 +- .../main/java/me/impy/aegis/AuthActivity.java | 17 ++++--- .../impy/aegis/CustomAuthenticatedSlide.java | 12 ++--- .../impy/aegis/FingerprintDialogFragment.java | 8 ++-- .../me/impy/aegis/SlotManagerActivity.java | 39 ++++++++++++---- .../me/impy/aegis/crypto/CryptoUtils.java | 2 +- .../me/impy/aegis/crypto/KeyStoreHandle.java | 17 +++---- .../impy/aegis/crypto/slots/PasswordSlot.java | 2 +- .../me/impy/aegis/crypto/slots/RawSlot.java | 5 +- .../java/me/impy/aegis/crypto/slots/Slot.java | 13 +++++- app/src/main/java/me/impy/aegis/util/Hex.java | 46 +++++++++++++++++++ app/src/main/res/layout/card_slot.xml | 30 +++++++----- doc/db.md | 1 + 13 files changed, 143 insertions(+), 51 deletions(-) create mode 100644 app/src/main/java/me/impy/aegis/util/Hex.java diff --git a/.idea/misc.xml b/.idea/misc.xml index 635999df..53a3fb16 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -24,7 +24,7 @@ - + diff --git a/app/src/main/java/me/impy/aegis/AuthActivity.java b/app/src/main/java/me/impy/aegis/AuthActivity.java index 8fd31ebf..2cf21f9a 100644 --- a/app/src/main/java/me/impy/aegis/AuthActivity.java +++ b/app/src/main/java/me/impy/aegis/AuthActivity.java @@ -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); diff --git a/app/src/main/java/me/impy/aegis/CustomAuthenticatedSlide.java b/app/src/main/java/me/impy/aegis/CustomAuthenticatedSlide.java index 9ad940aa..e9ea2f73 100644 --- a/app/src/main/java/me/impy/aegis/CustomAuthenticatedSlide.java +++ b/app/src/main/java/me/impy/aegis/CustomAuthenticatedSlide.java @@ -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(); } } diff --git a/app/src/main/java/me/impy/aegis/FingerprintDialogFragment.java b/app/src/main/java/me/impy/aegis/FingerprintDialogFragment.java index 476d4f80..9caddb72 100644 --- a/app/src/main/java/me/impy/aegis/FingerprintDialogFragment.java +++ b/app/src/main/java/me/impy/aegis/FingerprintDialogFragment.java @@ -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(); } diff --git a/app/src/main/java/me/impy/aegis/SlotManagerActivity.java b/app/src/main/java/me/impy/aegis/SlotManagerActivity.java index 7c16a394..395f0356 100644 --- a/app/src/main/java/me/impy/aegis/SlotManagerActivity.java +++ b/app/src/main/java/me/impy/aegis/SlotManagerActivity.java @@ -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 diff --git a/app/src/main/java/me/impy/aegis/crypto/CryptoUtils.java b/app/src/main/java/me/impy/aegis/crypto/CryptoUtils.java index 14c8db3c..642350b8 100644 --- a/app/src/main/java/me/impy/aegis/crypto/CryptoUtils.java +++ b/app/src/main/java/me/impy/aegis/crypto/CryptoUtils.java @@ -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); diff --git a/app/src/main/java/me/impy/aegis/crypto/KeyStoreHandle.java b/app/src/main/java/me/impy/aegis/crypto/KeyStoreHandle.java index 91c5ae95..4488ca8c 100644 --- a/app/src/main/java/me/impy/aegis/crypto/KeyStoreHandle.java +++ b/app/src/main/java/me/impy/aegis/crypto/KeyStoreHandle.java @@ -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); } } diff --git a/app/src/main/java/me/impy/aegis/crypto/slots/PasswordSlot.java b/app/src/main/java/me/impy/aegis/crypto/slots/PasswordSlot.java index 37ad7827..5584ec8c 100644 --- a/app/src/main/java/me/impy/aegis/crypto/slots/PasswordSlot.java +++ b/app/src/main/java/me/impy/aegis/crypto/slots/PasswordSlot.java @@ -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 diff --git a/app/src/main/java/me/impy/aegis/crypto/slots/RawSlot.java b/app/src/main/java/me/impy/aegis/crypto/slots/RawSlot.java index 803b673d..03b116da 100644 --- a/app/src/main/java/me/impy/aegis/crypto/slots/RawSlot.java +++ b/app/src/main/java/me/impy/aegis/crypto/slots/RawSlot.java @@ -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 diff --git a/app/src/main/java/me/impy/aegis/crypto/slots/Slot.java b/app/src/main/java/me/impy/aegis/crypto/slots/Slot.java index fe887dfb..6ccf3f82 100644 --- a/app/src/main/java/me/impy/aegis/crypto/slots/Slot.java +++ b/app/src/main/java/me/impy/aegis/crypto/slots/Slot.java @@ -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(); diff --git a/app/src/main/java/me/impy/aegis/util/Hex.java b/app/src/main/java/me/impy/aegis/util/Hex.java new file mode 100644 index 00000000..34793984 --- /dev/null +++ b/app/src/main/java/me/impy/aegis/util/Hex.java @@ -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(); + } +} diff --git a/app/src/main/res/layout/card_slot.xml b/app/src/main/res/layout/card_slot.xml index cc72f4e7..86435f8f 100644 --- a/app/src/main/res/layout/card_slot.xml +++ b/app/src/main/res/layout/card_slot.xml @@ -24,20 +24,28 @@ android:layout_height="match_parent" android:layout_marginEnd="15dp"/> - + android:layout_weight="1"> - - + + + + + diff --git a/doc/db.md b/doc/db.md index c5c9ba15..2a8eec03 100644 --- a/doc/db.md +++ b/doc/db.md @@ -70,6 +70,7 @@ A slot has the following structure. | Length | Contents | |:-------|:--------------------| | `1` | `uint8_t` Type | +| `16` | ID | | `32` | Encrypted key | | `?` | Additional data |