Implement biometric encryption (WIP)

This commit is contained in:
MrLetsplay 2023-09-18 18:25:13 +02:00
parent 9259f669cf
commit 8fbf44b75a
Signed by: mr
SSH Key Fingerprint: SHA256:92jBH80vpXyaZHjaIl47pjRq+Yt7XGTArqQg1V7hSqg
4 changed files with 82 additions and 40 deletions

View File

@ -69,8 +69,6 @@ public class MainActivity extends BaseActivity {
setLocale(SettingsUtil.getLocale(this)); setLocale(SettingsUtil.getLocale(this));
OTPDatabase.promptLoadDatabase(this, this::launchApp, this::finishAffinity);
startQRCodeScan = registerForActivityResult(new QRScannerContract(), obj -> { startQRCodeScan = registerForActivityResult(new QRScannerContract(), obj -> {
if(obj == null) return; // Cancelled if(obj == null) return; // Cancelled
@ -85,6 +83,8 @@ public class MainActivity extends BaseActivity {
for(OTPData d : obj.getData()) frag.addOTP(d); for(OTPData d : obj.getData()) frag.addOTP(d);
} }
}); });
OTPDatabase.promptLoadDatabase(this, this::launchApp, this::finishAffinity);
} }
public void setLocale(Locale locale) { public void setLocale(Locale locale) {

View File

@ -59,7 +59,8 @@ public class OTPListAdapter extends RecyclerView.Adapter<OTPListItem> {
holder.getBinding().progress.setVisibility(data.getType() == OTPType.TOTP ? View.VISIBLE : View.INVISIBLE); holder.getBinding().progress.setVisibility(data.getType() == OTPType.TOTP ? View.VISIBLE : View.INVISIBLE);
holder.getBinding().getRoot().setOnClickListener(view -> { holder.getBinding().getRoot().setOnClickListener(view -> {
Log.i("CLICKED", "CLICKED: " + view.isClickable()); if(!view.isClickable()) return;
if(data.getType() != OTPType.HOTP) return; if(data.getType() != OTPType.HOTP) return;
// Click delay for HOTP // Click delay for HOTP

View File

@ -5,6 +5,9 @@ import static androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTI
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; 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.NonNull;
import androidx.annotation.Nullable; 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.SettingsUtil;
import com.cringe_studios.cringe_authenticator.util.ThemeUtil; 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 java.util.concurrent.Executor;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
public class UnlockActivity extends AppCompatActivity { public class UnlockActivity extends AppCompatActivity {
@ -41,24 +50,33 @@ public class UnlockActivity extends AppCompatActivity {
ThemeUtil.loadTheme(this); ThemeUtil.loadTheme(this);
if(!SettingsUtil.isDatabaseEncrypted(this)) { if(!SettingsUtil.isDatabaseEncrypted(this)) {
launchApp(); success();
return; return;
} }
binding = ActivityUnlockBinding.inflate(getLayoutInflater()); binding = ActivityUnlockBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot()); 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)) { if(SettingsUtil.isBiometricLock(this)) {
Executor executor = ContextCompat.getMainExecutor(this); Executor executor = ContextCompat.getMainExecutor(this);
BiometricPrompt prompt = new BiometricPrompt(this, executor, new BiometricPrompt.AuthenticationCallback() { BiometricPrompt prompt = new BiometricPrompt(this, executor, new BiometricPrompt.AuthenticationCallback() {
@Override @Override
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) { public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
//finishAffinity(); failure();
} }
@Override @Override
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) { public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
launchApp(); success();
} }
}); });
@ -78,7 +96,7 @@ public class UnlockActivity extends AppCompatActivity {
//prompt.authenticate(info); //prompt.authenticate(info);
}); });
}else { }else {
launchApp(); success();
} }
} }
@ -93,7 +111,7 @@ public class UnlockActivity extends AppCompatActivity {
try { try {
SecretKey key = Crypto.generateKey(SettingsUtil.getCryptoParameters(this), password); SecretKey key = Crypto.generateKey(SettingsUtil.getCryptoParameters(this), password);
OTPDatabase.loadDatabase(this, key); OTPDatabase.loadDatabase(this, key);
launchApp(); success();
}catch(CryptoException e) { }catch(CryptoException e) {
DialogUtil.showErrorDialog(this, "Failed to load database: Invalid password or database corrupted", this::failure); DialogUtil.showErrorDialog(this, "Failed to load database: Invalid password or database corrupted", this::failure);
} catch (OTPDatabaseException e) { } catch (OTPDatabaseException e) {
@ -102,7 +120,7 @@ public class UnlockActivity extends AppCompatActivity {
}); });
} }
private void launchApp() { private void success() {
if(getIntent() != null && getIntent().hasExtra("contract")) { if(getIntent() != null && getIntent().hasExtra("contract")) {
setResult(RESULT_OK); setResult(RESULT_OK);
finish(); finish();

View File

@ -2,11 +2,14 @@ package com.cringe_studios.cringe_authenticator.util;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.util.Base64;
import com.cringe_studios.cringe_authenticator.R; import com.cringe_studios.cringe_authenticator.R;
import com.cringe_studios.cringe_authenticator.crypto.CryptoParameters; import com.cringe_studios.cringe_authenticator.crypto.CryptoParameters;
import com.google.gson.Gson; import com.google.gson.Gson;
import org.bouncycastle.jcajce.provider.symmetric.ARC4;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -85,28 +88,6 @@ public class SettingsUtil {
.apply(); .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) { public static String getGroupName(Context ctx, String group) {
return ctx.getSharedPreferences(GROUPS_PREFS_NAME, Context.MODE_PRIVATE).getString("group." + group + ".name", group); return ctx.getSharedPreferences(GROUPS_PREFS_NAME, Context.MODE_PRIVATE).getString("group." + group + ".name", group);
} }
@ -124,6 +105,57 @@ public class SettingsUtil {
.apply(); .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) { public static void setEnableIntroVideo(Context ctx, boolean enableIntroVideo) {
SharedPreferences prefs = ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE); SharedPreferences prefs = ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE);
prefs.edit().putBoolean("enableIntroVideo", enableIntroVideo).apply(); 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); 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) { public static void setTheme(Context ctx, String theme) {
SharedPreferences prefs = ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE); SharedPreferences prefs = ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE);
prefs.edit().putString("theme", theme).apply(); prefs.edit().putString("theme", theme).apply();