mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-04-29 18:18:11 +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>
|
</value>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</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" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
|
|
|
@ -61,12 +61,17 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
|
||||||
FingerprintManager manager = FingerprintHelper.getManager(this);
|
FingerprintManager manager = FingerprintHelper.getManager(this);
|
||||||
if (manager != null && _slots.has(FingerprintSlot.class)) {
|
if (manager != null && _slots.has(FingerprintSlot.class)) {
|
||||||
try {
|
try {
|
||||||
KeyStoreHandle handle = new KeyStoreHandle();
|
// find a fingerprint slot with an id that matches an alias in the keystore
|
||||||
if (handle.keyExists()) {
|
for (FingerprintSlot slot : _slots.findAll(FingerprintSlot.class)) {
|
||||||
SecretKey key = handle.getKey();
|
String id = slot.getID();
|
||||||
_fingerCipher = Slot.createCipher(key, Cipher.DECRYPT_MODE);
|
KeyStoreHandle handle = new KeyStoreHandle();
|
||||||
_fingerHelper = new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, this);
|
if (handle.containsKey(id)) {
|
||||||
boxFingerprint.setVisibility(View.VISIBLE);
|
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) {
|
} catch (Exception e) {
|
||||||
throw new UndeclaredThrowableException(e);
|
throw new UndeclaredThrowableException(e);
|
||||||
|
|
|
@ -24,6 +24,7 @@ import javax.crypto.Cipher;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
|
|
||||||
import me.impy.aegis.crypto.KeyStoreHandle;
|
import me.impy.aegis.crypto.KeyStoreHandle;
|
||||||
|
import me.impy.aegis.crypto.slots.FingerprintSlot;
|
||||||
import me.impy.aegis.crypto.slots.Slot;
|
import me.impy.aegis.crypto.slots.Slot;
|
||||||
import me.impy.aegis.helpers.FingerprintUiHelper;
|
import me.impy.aegis.helpers.FingerprintUiHelper;
|
||||||
import me.impy.aegis.helpers.AuthHelper;
|
import me.impy.aegis.helpers.AuthHelper;
|
||||||
|
@ -39,6 +40,7 @@ public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiH
|
||||||
private TextView _textFingerprint;
|
private TextView _textFingerprint;
|
||||||
private FingerprintUiHelper _fingerHelper;
|
private FingerprintUiHelper _fingerHelper;
|
||||||
private KeyStoreHandle _storeHandle;
|
private KeyStoreHandle _storeHandle;
|
||||||
|
private FingerprintSlot _slot;
|
||||||
private Cipher _fingerCipher;
|
private Cipher _fingerCipher;
|
||||||
private boolean _fingerAuthenticated;
|
private boolean _fingerAuthenticated;
|
||||||
|
|
||||||
|
@ -93,13 +95,9 @@ public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiH
|
||||||
try {
|
try {
|
||||||
if (_storeHandle == null) {
|
if (_storeHandle == null) {
|
||||||
_storeHandle = new KeyStoreHandle();
|
_storeHandle = new KeyStoreHandle();
|
||||||
|
_slot = new FingerprintSlot();
|
||||||
}
|
}
|
||||||
// TODO: consider regenerating the key here if it already exists
|
key = _storeHandle.generateKey(_slot.getID());
|
||||||
if (!_storeHandle.keyExists()) {
|
|
||||||
key = _storeHandle.generateKey(true);
|
|
||||||
} else {
|
|
||||||
key = _storeHandle.getKey();
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new UndeclaredThrowableException(e);
|
throw new UndeclaredThrowableException(e);
|
||||||
}
|
}
|
||||||
|
@ -160,7 +158,7 @@ public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiH
|
||||||
|
|
||||||
View view = getView();
|
View view = getView();
|
||||||
if (view != null) {
|
if (view != null) {
|
||||||
Snackbar snackbar = Snackbar.make(getView(), message, Snackbar.LENGTH_LONG);
|
Snackbar snackbar = Snackbar.make(view, message, Snackbar.LENGTH_LONG);
|
||||||
snackbar.show();
|
snackbar.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import me.impy.aegis.helpers.FingerprintUiHelper;
|
||||||
public class FingerprintDialogFragment extends SlotDialogFragment implements FingerprintUiHelper.Callback {
|
public class FingerprintDialogFragment extends SlotDialogFragment implements FingerprintUiHelper.Callback {
|
||||||
private Cipher _cipher;
|
private Cipher _cipher;
|
||||||
private FingerprintUiHelper _helper;
|
private FingerprintUiHelper _helper;
|
||||||
|
private FingerprintSlot _slot;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
|
@ -32,8 +33,8 @@ public class FingerprintDialogFragment extends SlotDialogFragment implements Fin
|
||||||
|
|
||||||
FingerprintManager manager = FingerprintHelper.getManager(getContext());
|
FingerprintManager manager = FingerprintHelper.getManager(getContext());
|
||||||
try {
|
try {
|
||||||
KeyStoreHandle handle = new KeyStoreHandle();
|
_slot = new FingerprintSlot();
|
||||||
SecretKey key = handle.getKey();
|
SecretKey key = new KeyStoreHandle().generateKey(_slot.getID());
|
||||||
_cipher = Slot.createCipher(key, Cipher.ENCRYPT_MODE);
|
_cipher = Slot.createCipher(key, Cipher.ENCRYPT_MODE);
|
||||||
_helper = new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, this);
|
_helper = new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, this);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -67,8 +68,7 @@ public class FingerprintDialogFragment extends SlotDialogFragment implements Fin
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAuthenticated() {
|
public void onAuthenticated() {
|
||||||
FingerprintSlot slot = new FingerprintSlot();
|
getListener().onSlotResult(_slot, _cipher);
|
||||||
getListener().onSlotResult(slot, _cipher);
|
|
||||||
dismiss();
|
dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,9 @@ import android.widget.Toast;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
|
|
||||||
|
import me.impy.aegis.crypto.KeyStoreHandle;
|
||||||
import me.impy.aegis.crypto.MasterKey;
|
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.PasswordSlot;
|
||||||
import me.impy.aegis.crypto.slots.Slot;
|
import me.impy.aegis.crypto.slots.Slot;
|
||||||
import me.impy.aegis.crypto.slots.SlotCollection;
|
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.setHomeAsUpIndicator(R.drawable.ic_close);
|
||||||
bar.setDisplayHomeAsUpEnabled(true);
|
bar.setDisplayHomeAsUpEnabled(true);
|
||||||
|
|
||||||
// only show the fingerprint option if we can get an instance of the fingerprint manager
|
findViewById(R.id.button_add_fingerprint).setOnClickListener(view -> {
|
||||||
// TODO: also hide the option if this device's fingerprint has already been registered
|
FingerprintDialogFragment dialog = new FingerprintDialogFragment();
|
||||||
if (FingerprintHelper.getManager(this) != null) {
|
dialog.show(getSupportFragmentManager(), 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_password).setOnClickListener(view -> {
|
findViewById(R.id.button_add_password).setOnClickListener(view -> {
|
||||||
PasswordDialogFragment dialog = new PasswordDialogFragment();
|
PasswordDialogFragment dialog = new PasswordDialogFragment();
|
||||||
|
@ -66,6 +62,26 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
|
||||||
for (Slot slot : _slots) {
|
for (Slot slot : _slots) {
|
||||||
_adapter.addSlot(slot);
|
_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() {
|
private boolean onSave() {
|
||||||
|
@ -132,6 +148,7 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
|
||||||
.setPositiveButton(android.R.string.yes, (dialog, whichButton) -> {
|
.setPositiveButton(android.R.string.yes, (dialog, whichButton) -> {
|
||||||
_slots.remove(slot);
|
_slots.remove(slot);
|
||||||
_adapter.removeSlot(slot);
|
_adapter.removeSlot(slot);
|
||||||
|
updateFingerprintButton();
|
||||||
})
|
})
|
||||||
.setNegativeButton(android.R.string.no, null)
|
.setNegativeButton(android.R.string.no, null)
|
||||||
.show();
|
.show();
|
||||||
|
@ -145,8 +162,10 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
|
||||||
onException(e);
|
onException(e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_slots.add(slot);
|
_slots.add(slot);
|
||||||
_adapter.addSlot(slot);
|
_adapter.addSlot(slot);
|
||||||
|
updateFingerprintButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -120,7 +120,7 @@ public class CryptoUtils {
|
||||||
return generateRandomBytes(CRYPTO_NONCE_SIZE);
|
return generateRandomBytes(CRYPTO_NONCE_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] generateRandomBytes(int length) {
|
public static byte[] generateRandomBytes(int length) {
|
||||||
SecureRandom random = new SecureRandom();
|
SecureRandom random = new SecureRandom();
|
||||||
byte[] data = new byte[length];
|
byte[] data = new byte[length];
|
||||||
random.nextBytes(data);
|
random.nextBytes(data);
|
||||||
|
|
|
@ -14,9 +14,10 @@ import java.security.cert.CertificateException;
|
||||||
import javax.crypto.KeyGenerator;
|
import javax.crypto.KeyGenerator;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
|
|
||||||
|
import me.impy.aegis.crypto.slots.FingerprintSlot;
|
||||||
|
|
||||||
public class KeyStoreHandle {
|
public class KeyStoreHandle {
|
||||||
private final KeyStore _keyStore;
|
private final KeyStore _keyStore;
|
||||||
private static final String KEY_NAME = "AegisKey";
|
|
||||||
private static final String STORE_NAME = "AndroidKeyStore";
|
private static final String STORE_NAME = "AndroidKeyStore";
|
||||||
|
|
||||||
public KeyStoreHandle() throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
|
public KeyStoreHandle() throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
|
||||||
|
@ -24,18 +25,18 @@ public class KeyStoreHandle {
|
||||||
_keyStore.load(null);
|
_keyStore.load(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean keyExists() throws KeyStoreException {
|
public boolean containsKey(String id) throws KeyStoreException {
|
||||||
return _keyStore.containsAlias(KEY_NAME);
|
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) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
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(KEY_NAME,
|
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_ECB)
|
||||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
||||||
.setUserAuthenticationRequired(authRequired)
|
.setUserAuthenticationRequired(true)
|
||||||
.setRandomizedEncryptionRequired(false)
|
.setRandomizedEncryptionRequired(false)
|
||||||
.setKeySize(CryptoUtils.CRYPTO_KEY_SIZE * 8)
|
.setKeySize(CryptoUtils.CRYPTO_KEY_SIZE * 8)
|
||||||
.build());
|
.build());
|
||||||
|
@ -46,7 +47,7 @@ public class KeyStoreHandle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public SecretKey getKey() throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException {
|
public SecretKey getKey(String id) throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException {
|
||||||
return (SecretKey) _keyStore.getKey(KEY_NAME, null);
|
return (SecretKey) _keyStore.getKey(id, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,7 @@ public class PasswordSlot extends RawSlot {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getSize() {
|
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
|
@Override
|
||||||
|
|
|
@ -13,6 +13,7 @@ public class RawSlot extends Slot {
|
||||||
public byte[] serialize() {
|
public byte[] serialize() {
|
||||||
LittleByteBuffer buffer = LittleByteBuffer.allocate(getSize());
|
LittleByteBuffer buffer = LittleByteBuffer.allocate(getSize());
|
||||||
buffer.put(getType());
|
buffer.put(getType());
|
||||||
|
buffer.put(_id);
|
||||||
buffer.put(_encryptedMasterKey);
|
buffer.put(_encryptedMasterKey);
|
||||||
return buffer.array();
|
return buffer.array();
|
||||||
}
|
}
|
||||||
|
@ -23,13 +24,15 @@ public class RawSlot extends Slot {
|
||||||
if (buffer.get() != getType()) {
|
if (buffer.get() != getType()) {
|
||||||
throw new Exception("slot type mismatch");
|
throw new Exception("slot type mismatch");
|
||||||
}
|
}
|
||||||
|
_id = new byte[ID_SIZE];
|
||||||
|
buffer.get(_id);
|
||||||
_encryptedMasterKey = new byte[CryptoUtils.CRYPTO_KEY_SIZE];
|
_encryptedMasterKey = new byte[CryptoUtils.CRYPTO_KEY_SIZE];
|
||||||
buffer.get(_encryptedMasterKey);
|
buffer.get(_encryptedMasterKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getSize() {
|
public int getSize() {
|
||||||
return 1 + CryptoUtils.CRYPTO_KEY_SIZE;
|
return 1 + ID_SIZE + CryptoUtils.CRYPTO_KEY_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -15,14 +15,21 @@ import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
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.util.Hex;
|
||||||
|
|
||||||
public abstract class Slot implements Serializable {
|
public abstract class Slot implements Serializable {
|
||||||
public final static byte TYPE_RAW = 0x00;
|
public final static byte TYPE_RAW = 0x00;
|
||||||
public final static byte TYPE_DERIVED = 0x01;
|
public final static byte TYPE_DERIVED = 0x01;
|
||||||
public final static byte TYPE_FINGERPRINT = 0x02;
|
public final static byte TYPE_FINGERPRINT = 0x02;
|
||||||
|
public final static int ID_SIZE = 16;
|
||||||
|
|
||||||
|
protected byte[] _id;
|
||||||
protected byte[] _encryptedMasterKey;
|
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.
|
// getKey decrypts the encrypted master key in this slot with the given key and returns it.
|
||||||
public SecretKey getKey(Cipher cipher) throws BadPaddingException, IllegalBlockSizeException {
|
public SecretKey getKey(Cipher cipher) throws BadPaddingException, IllegalBlockSizeException {
|
||||||
byte[] decryptedKeyBytes = cipher.doFinal(_encryptedMasterKey);
|
byte[] decryptedKeyBytes = cipher.doFinal(_encryptedMasterKey);
|
||||||
|
@ -38,7 +45,7 @@ public abstract class Slot implements Serializable {
|
||||||
CryptoUtils.zero(masterKeyBytes);
|
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
|
// this is perfectly safe because we discard this cipher after passing CryptoUtils.CRYPTO_KEY_SIZE bytes through it
|
||||||
@SuppressLint("getInstance")
|
@SuppressLint("getInstance")
|
||||||
public static Cipher createCipher(SecretKey key, int mode) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException {
|
public static Cipher createCipher(SecretKey key, int mode) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException {
|
||||||
|
@ -47,6 +54,10 @@ public abstract class Slot implements Serializable {
|
||||||
return cipher;
|
return cipher;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getID() {
|
||||||
|
return Hex.toString(_id);
|
||||||
|
}
|
||||||
|
|
||||||
public abstract int getSize();
|
public abstract int getSize();
|
||||||
public abstract byte getType();
|
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_height="match_parent"
|
||||||
android:layout_marginEnd="15dp"/>
|
android:layout_marginEnd="15dp"/>
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
|
||||||
android:text="Slot name"
|
|
||||||
android:id="@+id/text_slot_name"
|
|
||||||
android:textColor="@color/primary_text"
|
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"/>
|
android:layout_weight="1">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/text_slot_used"
|
android:layout_width="wrap_content"
|
||||||
android:layout_width="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content">
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
</TextView>
|
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>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,7 @@ A slot has the following structure.
|
||||||
| Length | Contents |
|
| Length | Contents |
|
||||||
|:-------|:--------------------|
|
|:-------|:--------------------|
|
||||||
| `1` | `uint8_t` Type |
|
| `1` | `uint8_t` Type |
|
||||||
|
| `16` | ID |
|
||||||
| `32` | Encrypted key |
|
| `32` | Encrypted key |
|
||||||
| `?` | Additional data |
|
| `?` | Additional data |
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue