From 8fbf44b75a5d829bbb732e610be468f9ebc25e74 Mon Sep 17 00:00:00 2001 From: MrLetsplay Date: Mon, 18 Sep 2023 18:25:13 +0200 Subject: [PATCH] Implement biometric encryption (WIP) --- .../cringe_authenticator/MainActivity.java | 4 +- .../otplist/OTPListAdapter.java | 3 +- .../unlock/UnlockActivity.java | 30 +++++-- .../util/SettingsUtil.java | 85 ++++++++++++------- 4 files changed, 82 insertions(+), 40 deletions(-) diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/MainActivity.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/MainActivity.java index e6d736d..396e621 100644 --- a/app/src/main/java/com/cringe_studios/cringe_authenticator/MainActivity.java +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/MainActivity.java @@ -69,8 +69,6 @@ public class MainActivity extends BaseActivity { setLocale(SettingsUtil.getLocale(this)); - OTPDatabase.promptLoadDatabase(this, this::launchApp, this::finishAffinity); - startQRCodeScan = registerForActivityResult(new QRScannerContract(), obj -> { if(obj == null) return; // Cancelled @@ -85,6 +83,8 @@ public class MainActivity extends BaseActivity { for(OTPData d : obj.getData()) frag.addOTP(d); } }); + + OTPDatabase.promptLoadDatabase(this, this::launchApp, this::finishAffinity); } public void setLocale(Locale locale) { diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/otplist/OTPListAdapter.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/otplist/OTPListAdapter.java index cd6f801..519dfac 100644 --- a/app/src/main/java/com/cringe_studios/cringe_authenticator/otplist/OTPListAdapter.java +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/otplist/OTPListAdapter.java @@ -59,7 +59,8 @@ public class OTPListAdapter extends RecyclerView.Adapter { holder.getBinding().progress.setVisibility(data.getType() == OTPType.TOTP ? View.VISIBLE : View.INVISIBLE); holder.getBinding().getRoot().setOnClickListener(view -> { - Log.i("CLICKED", "CLICKED: " + view.isClickable()); + if(!view.isClickable()) return; + if(data.getType() != OTPType.HOTP) return; // Click delay for HOTP diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/unlock/UnlockActivity.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/unlock/UnlockActivity.java index 4359af8..1de02dd 100644 --- a/app/src/main/java/com/cringe_studios/cringe_authenticator/unlock/UnlockActivity.java +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/unlock/UnlockActivity.java @@ -5,6 +5,9 @@ import static androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTI import android.content.Intent; import android.os.Bundle; +import android.security.KeyStoreParameter; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyProtection; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -24,8 +27,14 @@ import com.cringe_studios.cringe_authenticator.util.OTPDatabaseException; import com.cringe_studios.cringe_authenticator.util.SettingsUtil; import com.cringe_studios.cringe_authenticator.util.ThemeUtil; +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; import java.util.concurrent.Executor; +import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; public class UnlockActivity extends AppCompatActivity { @@ -41,24 +50,33 @@ public class UnlockActivity extends AppCompatActivity { ThemeUtil.loadTheme(this); if(!SettingsUtil.isDatabaseEncrypted(this)) { - launchApp(); + success(); return; } binding = ActivityUnlockBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); + try { + KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); + ks.load(null); + SecretKey key = KeyGenerator.getInstance("AES").generateKey(); + ks.setEntry("amogus", new KeyStore.SecretKeyEntry(key), new KeyStoreParameter.Builder(this).build()); + } catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + if(SettingsUtil.isBiometricLock(this)) { Executor executor = ContextCompat.getMainExecutor(this); BiometricPrompt prompt = new BiometricPrompt(this, executor, new BiometricPrompt.AuthenticationCallback() { @Override public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) { - //finishAffinity(); + failure(); } @Override public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) { - launchApp(); + success(); } }); @@ -78,7 +96,7 @@ public class UnlockActivity extends AppCompatActivity { //prompt.authenticate(info); }); }else { - launchApp(); + success(); } } @@ -93,7 +111,7 @@ public class UnlockActivity extends AppCompatActivity { try { SecretKey key = Crypto.generateKey(SettingsUtil.getCryptoParameters(this), password); OTPDatabase.loadDatabase(this, key); - launchApp(); + success(); }catch(CryptoException e) { DialogUtil.showErrorDialog(this, "Failed to load database: Invalid password or database corrupted", this::failure); } catch (OTPDatabaseException e) { @@ -102,7 +120,7 @@ public class UnlockActivity extends AppCompatActivity { }); } - private void launchApp() { + private void success() { if(getIntent() != null && getIntent().hasExtra("contract")) { setResult(RESULT_OK); finish(); diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/util/SettingsUtil.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/util/SettingsUtil.java index f224c9b..717a6d3 100644 --- a/app/src/main/java/com/cringe_studios/cringe_authenticator/util/SettingsUtil.java +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/util/SettingsUtil.java @@ -2,11 +2,14 @@ package com.cringe_studios.cringe_authenticator.util; import android.content.Context; import android.content.SharedPreferences; +import android.util.Base64; import com.cringe_studios.cringe_authenticator.R; import com.cringe_studios.cringe_authenticator.crypto.CryptoParameters; import com.google.gson.Gson; +import org.bouncycastle.jcajce.provider.symmetric.ARC4; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -85,28 +88,6 @@ public class SettingsUtil { .apply(); }*/ - public static boolean isDatabaseEncrypted(Context ctx) { - return ctx.getSharedPreferences(GROUPS_PREFS_NAME, Context.MODE_PRIVATE).getBoolean("encryption", false); - } - - public static void enableEncryption(Context ctx, CryptoParameters parameters) { - ctx.getSharedPreferences(GROUPS_PREFS_NAME, Context.MODE_PRIVATE).edit() - .putBoolean("encryption", true) - .putString("encryption.parameters", GSON.toJson(parameters)) - .apply(); - } - - public static void disableEncryption(Context ctx) { - ctx.getSharedPreferences(GROUPS_PREFS_NAME, Context.MODE_PRIVATE).edit() - .putBoolean("encryption", false) - .remove("encryption.parameters") - .apply(); - } - - public static CryptoParameters getCryptoParameters(Context ctx) { - return GSON.fromJson(ctx.getSharedPreferences(GROUPS_PREFS_NAME, Context.MODE_PRIVATE).getString("encryption.parameters", "{}"), CryptoParameters.class); - } - public static String getGroupName(Context ctx, String group) { return ctx.getSharedPreferences(GROUPS_PREFS_NAME, Context.MODE_PRIVATE).getString("group." + group + ".name", group); } @@ -124,6 +105,57 @@ public class SettingsUtil { .apply(); } + public static boolean isDatabaseEncrypted(Context ctx) { + return ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE).getBoolean("encryption", false); + } + + public static void enableEncryption(Context ctx, CryptoParameters parameters) { + ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE).edit() + .putBoolean("encryption", true) + .putString("encryption.parameters", GSON.toJson(parameters)) + .apply(); + } + + public static void disableEncryption(Context ctx) { + ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE).edit() + .putBoolean("encryption", false) + .remove("encryption.parameters") + .apply(); + } + + public static CryptoParameters getCryptoParameters(Context ctx) { + return GSON.fromJson(ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE).getString("encryption.parameters", "{}"), CryptoParameters.class); + } + + public static void enableBiometricEncryption(Context ctx, byte[] encryptedBiometricKey) { + ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE).edit() + .putBoolean("encryption.biometric", true) + .putString("encryption.biometric.key", Base64.encodeToString(encryptedBiometricKey, Base64.DEFAULT)) + .apply(); + } + + public static void disableBiometricEncryption(Context ctx) { + ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE).edit() + .putBoolean("encryption.biometric", false) + .remove("encryption.biometric.key") + .apply(); + } + + public static byte[] getEncryptedBiometricKey(Context ctx) { + String encoded = ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE).getString("encryption.biometric.key", null); + if(encoded == null) return null; + return Base64.decode(encoded, Base64.DEFAULT); + } + + /*public static void setBiometricLock(Context ctx, boolean biometricLock) { + SharedPreferences prefs = ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE); + prefs.edit().putBoolean("biometricLock", biometricLock).apply(); + } + + public static boolean isBiometricLock(Context ctx) { + return ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE).getBoolean("biometricLock", true); + }*/ + public static void setEnableIntroVideo(Context ctx, boolean enableIntroVideo) { SharedPreferences prefs = ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE); prefs.edit().putBoolean("enableIntroVideo", enableIntroVideo).apply(); @@ -133,15 +165,6 @@ public class SettingsUtil { return ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE).getBoolean("enableIntroVideo", true); } - public static void setBiometricLock(Context ctx, boolean biometricLock) { - SharedPreferences prefs = ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE); - prefs.edit().putBoolean("biometricLock", biometricLock).apply(); - } - - public static boolean isBiometricLock(Context ctx) { - return ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE).getBoolean("biometricLock", true); - } - public static void setTheme(Context ctx, String theme) { SharedPreferences prefs = ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE); prefs.edit().putString("theme", theme).apply();