Implement biometric encryption (WIP)
This commit is contained in:
parent
9259f669cf
commit
8fbf44b75a
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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();
|
||||||
|
@ -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();
|
||||||
|
Loading…
Reference in New Issue
Block a user