Replace the custom fingerprint auth UI with BiometricPrompt

This patch replaces the usage of the deprecated FingerprintManager API with
BiometricPrompt. This uses the Android X library, so we get the native biometric
prompt on recent versions of Android and a Google-made one on older versions. By
not working with custom prompts for biometric authentication like we do now, we
can be sure that any issues like #70, #81, #237 are not actually our fault.
Here's what it looks like:

![](https://alexbakker.me/u/b2rmf3x0b9.jpeg)

As a nice aside, this also adds support for the new facial recognition as an
authentication method on Pixel 4 phones.

This is still a draft, but early feedback is welcome.
This commit is contained in:
Alexander Bakker 2019-10-16 22:16:47 +02:00
parent a93ced6e34
commit 3be9aecb88
39 changed files with 499 additions and 716 deletions

View file

@ -4,17 +4,17 @@ import com.beemdevelopment.aegis.crypto.CryptParameters;
import java.util.UUID;
public class FingerprintSlot extends RawSlot {
public FingerprintSlot() {
public class BiometricSlot extends RawSlot {
public BiometricSlot() {
super();
}
FingerprintSlot(UUID uuid, byte[] key, CryptParameters keyParams) {
BiometricSlot(UUID uuid, byte[] key, CryptParameters keyParams) {
super(uuid, key, keyParams);
}
@Override
public byte getType() {
return TYPE_FINGERPRINT;
return TYPE_BIOMETRIC;
}
}

View file

@ -28,7 +28,7 @@ import javax.crypto.spec.SecretKeySpec;
public abstract class Slot extends UUIDMap.Value {
public final static byte TYPE_RAW = 0x00;
public final static byte TYPE_DERIVED = 0x01;
public final static byte TYPE_FINGERPRINT = 0x02;
public final static byte TYPE_BIOMETRIC = 0x02;
private byte[] _encryptedMasterKey;
private CryptParameters _encryptedMasterKeyParams;
@ -138,8 +138,8 @@ public abstract class Slot extends UUIDMap.Value {
boolean repaired = obj.optBoolean("repaired", false);
slot = new PasswordSlot(uuid, key, keyParams, scryptParams, repaired);
break;
case Slot.TYPE_FINGERPRINT:
slot = new FingerprintSlot(uuid, key, keyParams);
case Slot.TYPE_BIOMETRIC:
slot = new BiometricSlot(uuid, key, keyParams);
break;
default:
throw new SlotException("unrecognized slot type");

View file

@ -0,0 +1,136 @@
package com.beemdevelopment.aegis.helpers;
import androidx.annotation.NonNull;
import androidx.biometric.BiometricPrompt;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
import com.beemdevelopment.aegis.db.slots.BiometricSlot;
import com.beemdevelopment.aegis.db.slots.Slot;
import com.beemdevelopment.aegis.db.slots.SlotException;
import java.util.Objects;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
/**
* A class that can prepare initialization of a BiometricSlot by generating a new
* key in the Android KeyStore and authenticating a cipher for it through a
* BiometricPrompt.
*/
public class BiometricSlotInitializer extends BiometricPrompt.AuthenticationCallback {
private BiometricSlot _slot;
private Listener _listener;
private BiometricPrompt _prompt;
public BiometricSlotInitializer(Fragment fragment, Listener listener) {
_listener = listener;
_prompt = new BiometricPrompt(fragment, new UiThreadExecutor(), this);
}
public BiometricSlotInitializer(FragmentActivity activity, Listener listener) {
_listener = listener;
_prompt = new BiometricPrompt(activity, new UiThreadExecutor(), this);
}
/**
* Generates a new key in the Android KeyStore for the new BiometricSlot,
* initializes a cipher with it and shows a BiometricPrompt to the user for
* authentication. If authentication is successful, the new slot will be
* initialized and delivered back through the listener.
*/
public void authenticate(BiometricPrompt.PromptInfo info) {
if (_slot != null) {
throw new IllegalStateException("Biometric authentication already in progress");
}
KeyStoreHandle keyStore;
try {
keyStore = new KeyStoreHandle();
} catch (KeyStoreHandleException e) {
fail(e);
return;
}
// generate a new Android KeyStore key
// and assign it the UUID of the new slot as an alias
Cipher cipher;
BiometricSlot slot = new BiometricSlot();
try {
SecretKey key = keyStore.generateKey(slot.getUUID().toString());
cipher = Slot.createEncryptCipher(key);
} catch (KeyStoreHandleException | SlotException e) {
fail(e);
return;
}
_slot = slot;
_prompt.authenticate(info, new BiometricPrompt.CryptoObject(cipher));
}
/**
* Cancels the BiometricPrompt and resets the state of the initializer. It will
* also attempt to delete the previously generated Android KeyStore key.
*/
public void cancelAuthentication() {
if (_slot == null) {
throw new IllegalStateException("Biometric authentication not in progress");
}
reset();
_prompt.cancelAuthentication();
}
private void reset() {
if (_slot != null) {
try {
// clean up the unused KeyStore key
// this is non-critical, so just fail silently if an error occurs
String uuid = _slot.getUUID().toString();
KeyStoreHandle keyStore = new KeyStoreHandle();
if (keyStore.containsKey(uuid)) {
keyStore.deleteKey(uuid);
}
} catch (KeyStoreHandleException e) {
e.printStackTrace();
}
_slot = null;
}
}
private void fail(int errorCode, CharSequence errString) {
reset();
_listener.onSlotInitializationFailed(errorCode, errString);
}
private void fail(Exception e) {
e.printStackTrace();
fail(0, e.toString());
}
@Override
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
super.onAuthenticationError(errorCode, errString);
fail(errorCode, errString.toString());
}
@Override
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
super.onAuthenticationSucceeded(result);
_listener.onInitializeSlot(_slot, Objects.requireNonNull(result.getCryptoObject()).getCipher());
}
@Override
public void onAuthenticationFailed() {
super.onAuthenticationFailed();
}
public interface Listener {
void onInitializeSlot(BiometricSlot slot, Cipher cipher);
void onSlotInitializationFailed(int errorCode, @NonNull CharSequence errString);
}
}

View file

@ -0,0 +1,30 @@
package com.beemdevelopment.aegis.helpers;
import android.content.Context;
import androidx.biometric.BiometricConstants;
import androidx.biometric.BiometricManager;
public class BiometricsHelper {
private BiometricsHelper() {
}
public static BiometricManager getManager(Context context) {
BiometricManager manager = BiometricManager.from(context);
if (manager.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS) {
return manager;
}
return null;
}
public static boolean isCanceled(int errorCode) {
return errorCode == BiometricConstants.ERROR_CANCELED
|| errorCode == BiometricConstants.ERROR_USER_CANCELED
|| errorCode == BiometricConstants.ERROR_NEGATIVE_BUTTON;
}
public static boolean isAvailable(Context context) {
return getManager(context) != null;
}
}

View file

@ -1,34 +0,0 @@
package com.beemdevelopment.aegis.helpers;
import android.Manifest;
import android.content.Context;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
import androidx.annotation.RequiresApi;
public class FingerprintHelper {
private FingerprintHelper() {
}
@RequiresApi(api = Build.VERSION_CODES.M)
public static FingerprintManager getManager(Context context) {
if (PermissionHelper.granted(context, Manifest.permission.USE_FINGERPRINT)) {
FingerprintManager manager = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
if (manager != null && manager.isHardwareDetected() && manager.hasEnrolledFingerprints()) {
return manager;
}
}
return null;
}
@RequiresApi(api = Build.VERSION_CODES.M)
public static boolean isAvailable(Context context) {
return getManager(context) != null;
}
public static boolean isSupported() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
}
}

View file

@ -1,156 +0,0 @@
// This file was originally taken from https://github.com/googlesamples/android-FingerprintDialog/blob/2feb02945ae220ebd1bc2c2b620a1d43e30daea8/Application/src/main/java/com/example/android/fingerprintdialog/FingerprintUiHelper.java
// It has been modified to suit Aegis' needs
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.beemdevelopment.aegis.helpers;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
import android.os.CancellationSignal;
import android.widget.TextView;
import com.beemdevelopment.aegis.R;
import com.mattprecious.swirl.SwirlView;
import androidx.annotation.RequiresApi;
/**
* Small helper class to manage text/icon around fingerprint authentication UI.
*/
@RequiresApi(api = Build.VERSION_CODES.M)
public class FingerprintUiHelper extends FingerprintManager.AuthenticationCallback {
private static final long ERROR_TIMEOUT_MILLIS = 1600;
private static final long SUCCESS_DELAY_MILLIS = 100;
private final FingerprintManager mFingerprintManager;
private final SwirlView mIcon;
private final TextView mErrorTextView;
private final Callback mCallback;
private CancellationSignal mCancellationSignal;
private boolean mSelfCancelled;
/**
* Constructor for {@link FingerprintUiHelper}.
*/
public FingerprintUiHelper(FingerprintManager fingerprintManager,
SwirlView icon, TextView errorTextView, Callback callback) {
mFingerprintManager = fingerprintManager;
mIcon = icon;
mErrorTextView = errorTextView;
mCallback = callback;
}
public boolean isFingerprintAuthAvailable() {
// The line below prevents the false positive inspection from Android Studio
// noinspection ResourceType
return mFingerprintManager.isHardwareDetected()
&& mFingerprintManager.hasEnrolledFingerprints();
}
public void startListening(FingerprintManager.CryptoObject cryptoObject) {
if (!isFingerprintAuthAvailable()) {
return;
}
mCancellationSignal = new CancellationSignal();
mSelfCancelled = false;
// The line below prevents the false positive inspection from Android Studio
// noinspection ResourceType
mFingerprintManager
.authenticate(cryptoObject, mCancellationSignal, 0 /* flags */, this, null);
mIcon.setState(SwirlView.State.ON);
}
public void stopListening() {
if (mCancellationSignal != null) {
mSelfCancelled = true;
mCancellationSignal.cancel();
mCancellationSignal = null;
}
}
@Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
if (!mSelfCancelled) {
showError(errString);
mIcon.postDelayed(new Runnable() {
@Override
public void run() {
mCallback.onError();
}
}, ERROR_TIMEOUT_MILLIS);
}
}
@Override
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
showError(helpString);
}
@Override
public void onAuthenticationFailed() {
showError(mIcon.getResources().getString(
R.string.fingerprint_not_recognized));
}
@Override
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
mErrorTextView.removeCallbacks(mResetErrorTextRunnable);
mIcon.setState(SwirlView.State.OFF);
mErrorTextView.setText(
mErrorTextView.getResources().getString(R.string.fingerprint_success));
mIcon.postDelayed(new Runnable() {
@Override
public void run() {
mCallback.onAuthenticated();
}
}, SUCCESS_DELAY_MILLIS);
// ugly hack to keep the fingerprint icon visible while also giving visual feedback of success to the user
mIcon.postDelayed(new Runnable() {
@Override
public void run() {
mIcon.setState(SwirlView.State.ON);
}
}, 500);
}
private void showError(CharSequence error) {
mIcon.setState(SwirlView.State.ERROR);
mErrorTextView.setText(error);
mErrorTextView.removeCallbacks(mResetErrorTextRunnable);
mErrorTextView.postDelayed(mResetErrorTextRunnable, ERROR_TIMEOUT_MILLIS);
}
private Runnable mResetErrorTextRunnable = new Runnable() {
@Override
public void run() {
mErrorTextView.setText(
mErrorTextView.getResources().getString(R.string.fingerprint_hint));
mIcon.setState(SwirlView.State.ON);
}
};
public interface Callback {
void onAuthenticated();
void onError();
}
}

View file

@ -0,0 +1,17 @@
package com.beemdevelopment.aegis.helpers;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.NonNull;
import java.util.concurrent.Executor;
public class UiThreadExecutor implements Executor {
private final Handler _handler = new Handler(Looper.getMainLooper());
@Override
public void execute(@NonNull Runnable command) {
_handler.post(command);
}
}

View file

@ -1,61 +1,56 @@
package com.beemdevelopment.aegis.ui;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.biometric.BiometricPrompt;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.CancelAction;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
import com.beemdevelopment.aegis.db.DatabaseFileCredentials;
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
import com.beemdevelopment.aegis.db.slots.BiometricSlot;
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
import com.beemdevelopment.aegis.db.slots.Slot;
import com.beemdevelopment.aegis.db.slots.SlotException;
import com.beemdevelopment.aegis.db.slots.SlotList;
import com.beemdevelopment.aegis.helpers.BiometricsHelper;
import com.beemdevelopment.aegis.helpers.EditTextHelper;
import com.beemdevelopment.aegis.helpers.FingerprintHelper;
import com.beemdevelopment.aegis.helpers.FingerprintUiHelper;
import com.beemdevelopment.aegis.helpers.UiThreadExecutor;
import com.beemdevelopment.aegis.ui.tasks.SlotListTask;
import com.mattprecious.swirl.SwirlView;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import androidx.appcompat.app.AlertDialog;
public class AuthActivity extends AegisActivity implements FingerprintUiHelper.Callback, SlotListTask.Callback {
public class AuthActivity extends AegisActivity implements SlotListTask.Callback {
private EditText _textPassword;
private CancelAction _cancelAction;
private SlotList _slots;
private FingerprintUiHelper _fingerHelper;
private FingerprintManager.CryptoObject _fingerCryptoObj;
private BiometricPrompt.CryptoObject _bioCryptoObj;
private BiometricPrompt _bioPrompt;
@Override
@SuppressLint("NewApi")
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_auth);
_textPassword = findViewById(R.id.text_password);
LinearLayout boxFingerprint = findViewById(R.id.box_fingerprint);
LinearLayout boxFingerprintInfo = findViewById(R.id.box_fingerprint_info);
TextView textFingerprint = findViewById(R.id.text_fingerprint);
LinearLayout boxBiometricInfo = findViewById(R.id.box_biometric_info);
Button decryptButton = findViewById(R.id.button_decrypt);
Button biometricsButton = findViewById(R.id.button_biometrics);
_textPassword.setOnEditorActionListener((v, actionId, event) -> {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) {
@ -64,25 +59,17 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
return false;
});
SwirlView imgFingerprint = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
ViewGroup insertPoint = findViewById(R.id.img_fingerprint_insert);
imgFingerprint = new SwirlView(this);
insertPoint.addView(imgFingerprint, 0, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
}
Intent intent = getIntent();
_slots = (SlotList) intent.getSerializableExtra("slots");
_cancelAction = (CancelAction) intent.getSerializableExtra("cancelAction");
// only show the fingerprint controls if the api version is new enough, permission is granted, a scanner is found and a fingerprint slot is found
if (_slots.has(FingerprintSlot.class) && FingerprintHelper.isSupported() && FingerprintHelper.isAvailable(this)) {
// only show the biometric prompt if the api version is new enough, permission is granted, a scanner is found and a biometric slot is found
if (_slots.has(BiometricSlot.class) && BiometricsHelper.isAvailable(this)) {
boolean invalidated = false;
FingerprintManager manager = FingerprintHelper.getManager(this);
try {
// find a fingerprint slot with an id that matches an alias in the keystore
for (FingerprintSlot slot : _slots.findAll(FingerprintSlot.class)) {
// find a biometric slot with an id that matches an alias in the keystore
for (BiometricSlot slot : _slots.findAll(BiometricSlot.class)) {
String id = slot.getUUID().toString();
KeyStoreHandle handle = new KeyStoreHandle();
if (handle.containsKey(id)) {
@ -92,10 +79,11 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
invalidated = true;
continue;
}
Cipher cipher = slot.createDecryptCipher(key);
_fingerCryptoObj = new FingerprintManager.CryptoObject(cipher);
_fingerHelper = new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, this);
boxFingerprint.setVisibility(View.VISIBLE);
_bioCryptoObj = new BiometricPrompt.CryptoObject(cipher);
_bioPrompt = new BiometricPrompt(this, new UiThreadExecutor(), new BiometricPromptListener());
biometricsButton.setVisibility(View.VISIBLE);
invalidated = false;
break;
}
@ -106,7 +94,7 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
// display a help message if a matching invalidated keystore entry was found
if (invalidated) {
boxFingerprintInfo.setVisibility(View.VISIBLE);
boxBiometricInfo.setVisibility(View.VISIBLE);
}
}
@ -121,7 +109,11 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
}
});
if (_fingerHelper == null) {
biometricsButton.setOnClickListener(v -> {
showBiometricPrompt();
});
if (_bioCryptoObj == null) {
_textPassword.requestFocus();
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
}
@ -144,7 +136,7 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
private void selectPassword() {
_textPassword.selectAll();
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
}
@ -162,36 +154,31 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
}
@Override
@SuppressLint("NewApi")
public void onResume() {
super.onResume();
if (_fingerHelper != null) {
_fingerHelper.startListening(_fingerCryptoObj);
if (_bioPrompt != null) {
showBiometricPrompt();
}
}
public void showBiometricPrompt() {
BiometricPrompt.PromptInfo info = new BiometricPrompt.PromptInfo.Builder()
.setTitle(getString(R.string.authentication))
.setNegativeButtonText(getString(android.R.string.cancel))
.build();
_bioPrompt.authenticate(info, _bioCryptoObj);
}
@Override
@SuppressLint("NewApi")
public void onPause() {
super.onPause();
if (_fingerHelper != null) {
_fingerHelper.stopListening();
if (_bioPrompt != null) {
_bioPrompt.cancelAuthentication();
}
}
@Override
@SuppressLint("NewApi")
public void onAuthenticated() {
trySlots(FingerprintSlot.class, _fingerCryptoObj.getCipher());
}
@Override
public void onError() {
}
@Override
public void onTaskFinished(SlotListTask.Result result) {
if (result != null) {
@ -210,4 +197,25 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
showError();
}
}
private class BiometricPromptListener extends BiometricPrompt.AuthenticationCallback {
@Override
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
super.onAuthenticationError(errorCode, errString);
if (!BiometricsHelper.isCanceled(errorCode)) {
Toast.makeText(AuthActivity.this, errString, Toast.LENGTH_LONG).show();
}
}
@Override
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
super.onAuthenticationSucceeded(result);
trySlots(BiometricSlot.class, _bioCryptoObj.getCipher());
}
@Override
public void onAuthenticationFailed() {
super.onAuthenticationFailed();
}
}
}

View file

@ -4,8 +4,6 @@ import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
@ -18,30 +16,21 @@ import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.NumberPicker;
import android.widget.TextView;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import com.beemdevelopment.aegis.Preferences;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
import com.beemdevelopment.aegis.db.slots.Slot;
import com.beemdevelopment.aegis.db.slots.SlotException;
import com.beemdevelopment.aegis.helpers.EditTextHelper;
import com.beemdevelopment.aegis.helpers.FingerprintHelper;
import com.beemdevelopment.aegis.helpers.FingerprintUiHelper;
import com.beemdevelopment.aegis.ui.tasks.DerivationTask;
import com.mattprecious.swirl.SwirlView;
import java.util.concurrent.atomic.AtomicReference;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import androidx.annotation.RequiresApi;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
public class Dialogs {
private Dialogs() {
@ -195,52 +184,6 @@ public class Dialogs {
showSecureDialog(dialog);
}
@RequiresApi(api = Build.VERSION_CODES.M)
public static void showFingerprintDialog(Activity activity, Dialogs.SlotListener listener) {
View view = activity.getLayoutInflater().inflate(R.layout.dialog_fingerprint, null);
TextView textFingerprint = view.findViewById(R.id.text_fingerprint);
SwirlView imgFingerprint = view.findViewById(R.id.img_fingerprint);
FingerprintManager.CryptoObject obj;
FingerprintSlot slot;
final AtomicReference<FingerprintUiHelper> helper = new AtomicReference<>();
FingerprintManager manager = FingerprintHelper.getManager(activity);
try {
slot = new FingerprintSlot();
SecretKey key = new KeyStoreHandle().generateKey(slot.getUUID().toString());
Cipher cipher = Slot.createEncryptCipher(key);
obj = new FingerprintManager.CryptoObject(cipher);
} catch (KeyStoreHandleException | SlotException e) {
throw new RuntimeException(e);
}
AlertDialog dialog = new AlertDialog.Builder(activity)
.setTitle(R.string.register_fingerprint)
.setView(view)
.setNegativeButton(android.R.string.cancel, null)
.setOnDismissListener(d -> {
helper.get().stopListening();
})
.create();
helper.set(new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, new FingerprintUiHelper.Callback() {
@Override
public void onAuthenticated() {
listener.onSlotResult(slot, obj.getCipher());
dialog.dismiss();
}
@Override
public void onError() {
}
}));
helper.get().startListening(obj);
showSecureDialog(dialog);
}
public interface NumberInputListener {
void onNumberInputResult(int number);
}

View file

@ -4,6 +4,8 @@ import android.content.Intent;
import android.os.Bundle;
import android.view.WindowManager;
import androidx.fragment.app.Fragment;
import com.beemdevelopment.aegis.Preferences;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.db.Database;
@ -12,11 +14,10 @@ import com.beemdevelopment.aegis.db.DatabaseFileCredentials;
import com.beemdevelopment.aegis.db.DatabaseFileException;
import com.beemdevelopment.aegis.db.DatabaseManager;
import com.beemdevelopment.aegis.db.DatabaseManagerException;
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
import com.beemdevelopment.aegis.db.slots.BiometricSlot;
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
import com.beemdevelopment.aegis.db.slots.Slot;
import com.beemdevelopment.aegis.db.slots.SlotException;
import com.beemdevelopment.aegis.db.slots.SlotList;
import com.beemdevelopment.aegis.ui.slides.CustomAuthenticatedSlide;
import com.beemdevelopment.aegis.ui.slides.CustomAuthenticationSlide;
import com.beemdevelopment.aegis.ui.tasks.DerivationTask;
@ -29,8 +30,6 @@ import org.json.JSONObject;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import androidx.fragment.app.Fragment;
public class IntroActivity extends AppIntro2 implements DerivationTask.Callback {
public static final int RESULT_OK = 0;
public static final int RESULT_EXCEPTION = 1;
@ -135,7 +134,6 @@ public class IntroActivity extends AppIntro2 implements DerivationTask.Callback
creds = new DatabaseFileCredentials();
}
SlotList slots = null;
if (cryptType != CustomAuthenticationSlide.CRYPT_TYPE_NONE) {
// encrypt the master key with a key derived from the user's password
// and add it to the list of slots
@ -150,18 +148,14 @@ public class IntroActivity extends AppIntro2 implements DerivationTask.Callback
}
}
if (cryptType == CustomAuthenticationSlide.CRYPT_TYPE_FINGER) {
if (cryptType == CustomAuthenticationSlide.CRYPT_TYPE_BIOMETRIC) {
BiometricSlot slot = _authenticatedSlide.getBiometricSlot();
try {
// encrypt the master key with the fingerprint key
// and add it to the list of slots
FingerprintSlot slot = _authenticatedSlide.getFingerSlot();
Cipher cipher = _authenticatedSlide.getFingerCipher();
slot.setKey(creds.getKey(), cipher);
creds.getSlots().add(slot);
slot.setKey(creds.getKey(), _authenticatedSlide.getBiometriCipher());
} catch (SlotException e) {
setException(e);
return;
}
creds.getSlots().add(slot);
}
// finally, save the database

View file

@ -12,6 +12,12 @@ import android.view.Window;
import android.view.WindowManager;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.biometric.BiometricPrompt;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import com.beemdevelopment.aegis.AegisApplication;
import com.beemdevelopment.aegis.BuildConfig;
import com.beemdevelopment.aegis.CancelAction;
@ -25,12 +31,13 @@ import com.beemdevelopment.aegis.db.DatabaseFileCredentials;
import com.beemdevelopment.aegis.db.DatabaseFileException;
import com.beemdevelopment.aegis.db.DatabaseManager;
import com.beemdevelopment.aegis.db.DatabaseManagerException;
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
import com.beemdevelopment.aegis.db.slots.BiometricSlot;
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
import com.beemdevelopment.aegis.db.slots.Slot;
import com.beemdevelopment.aegis.db.slots.SlotException;
import com.beemdevelopment.aegis.db.slots.SlotList;
import com.beemdevelopment.aegis.helpers.FingerprintHelper;
import com.beemdevelopment.aegis.helpers.BiometricSlotInitializer;
import com.beemdevelopment.aegis.helpers.BiometricsHelper;
import com.beemdevelopment.aegis.helpers.PermissionHelper;
import com.beemdevelopment.aegis.importers.AegisImporter;
import com.beemdevelopment.aegis.importers.DatabaseImporter;
@ -56,10 +63,6 @@ import java.util.concurrent.atomic.AtomicReference;
import javax.crypto.Cipher;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
public class PreferencesFragment extends PreferenceFragmentCompat {
// activity request codes
private static final int CODE_IMPORT = 0;
@ -81,7 +84,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
private UUIDMap<DatabaseEntry> _importerEntries;
private SwitchPreference _encryptionPreference;
private SwitchPreference _fingerprintPreference;
private SwitchPreference _biometricsPreference;
private Preference _autoLockPreference;
private Preference _setPasswordPreference;
private Preference _slotsPreference;
@ -289,20 +292,25 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
}
});
_fingerprintPreference = (SwitchPreference) findPreference("pref_fingerprint");
_fingerprintPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
_biometricsPreference = (SwitchPreference) findPreference("pref_biometrics");
_biometricsPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
DatabaseFileCredentials creds = _db.getCredentials();
SlotList slots = creds.getSlots();
if (!slots.has(FingerprintSlot.class)) {
if (FingerprintHelper.isSupported() && FingerprintHelper.isAvailable(getContext())) {
Dialogs.showFingerprintDialog(getActivity(), new RegisterFingerprintListener());
if (!slots.has(BiometricSlot.class)) {
if (BiometricsHelper.isAvailable(getContext())) {
BiometricSlotInitializer initializer = new BiometricSlotInitializer(PreferencesFragment.this, new RegisterBiometricsListener());
BiometricPrompt.PromptInfo info = new BiometricPrompt.PromptInfo.Builder()
.setTitle(getString(R.string.set_up_biometric))
.setNegativeButtonText(getString(android.R.string.cancel))
.build();
initializer.authenticate(info);
}
} else {
// remove the fingerprint slot
FingerprintSlot slot = slots.find(FingerprintSlot.class);
// remove the biometric slot
BiometricSlot slot = slots.find(BiometricSlot.class);
slots.remove(slot);
_db.setCredentials(creds);
@ -676,24 +684,24 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
boolean encrypted = _db.isEncryptionEnabled();
_encryptionPreference.setChecked(encrypted, true);
_setPasswordPreference.setVisible(encrypted);
_fingerprintPreference.setVisible(encrypted);
_biometricsPreference.setVisible(encrypted);
_slotsPreference.setEnabled(encrypted);
_autoLockPreference.setVisible(encrypted);
if (encrypted) {
SlotList slots = _db.getCredentials().getSlots();
boolean multiPassword = slots.findAll(PasswordSlot.class).size() > 1;
boolean multiFinger = slots.findAll(FingerprintSlot.class).size() > 1;
boolean showSlots = BuildConfig.DEBUG || multiPassword || multiFinger;
boolean canUseFinger = FingerprintHelper.isSupported() && FingerprintHelper.isAvailable(getContext());
boolean multiBio = slots.findAll(BiometricSlot.class).size() > 1;
boolean showSlots = BuildConfig.DEBUG || multiPassword || multiBio;
boolean canUseBio = BiometricsHelper.isAvailable(getContext());
_setPasswordPreference.setEnabled(!multiPassword);
_fingerprintPreference.setEnabled(canUseFinger && !multiFinger);
_fingerprintPreference.setChecked(slots.has(FingerprintSlot.class), true);
_biometricsPreference.setEnabled(canUseBio && !multiBio);
_biometricsPreference.setChecked(slots.has(BiometricSlot.class), true);
_slotsPreference.setVisible(showSlots);
} else {
_setPasswordPreference.setEnabled(false);
_fingerprintPreference.setEnabled(false);
_fingerprintPreference.setChecked(false, true);
_biometricsPreference.setEnabled(false);
_biometricsPreference.setChecked(false, true);
_slotsPreference.setVisible(false);
}
}
@ -730,20 +738,17 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
}
}
private class RegisterFingerprintListener implements Dialogs.SlotListener {
private class RegisterBiometricsListener implements BiometricSlotInitializer.Listener {
@Override
public void onSlotResult(Slot slot, Cipher cipher) {
public void onInitializeSlot(BiometricSlot slot, Cipher cipher) {
DatabaseFileCredentials creds = _db.getCredentials();
SlotList slots = creds.getSlots();
try {
slot.setKey(creds.getKey(), cipher);
} catch (SlotException e) {
onException(e);
onSlotInitializationFailed(0, e.toString());
return;
}
slots.add(slot);
creds.getSlots().add(slot);
_db.setCredentials(creds);
saveDatabase();
@ -751,8 +756,10 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
}
@Override
public void onException(Exception e) {
Toast.makeText(getActivity(), getString(R.string.encryption_enable_fingerprint_error) + e.getMessage(), Toast.LENGTH_SHORT).show();
public void onSlotInitializationFailed(int errorCode, @NonNull CharSequence errString) {
if (!BiometricsHelper.isCanceled(errorCode)) {
Toast.makeText(getActivity(), String.format("%s: %s", getString(R.string.encryption_enable_biometrics_error), errString), Toast.LENGTH_LONG).show();
}
}
}

View file

@ -7,26 +7,29 @@ import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.biometric.BiometricPrompt;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
import com.beemdevelopment.aegis.db.DatabaseFileCredentials;
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
import com.beemdevelopment.aegis.db.slots.BiometricSlot;
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
import com.beemdevelopment.aegis.db.slots.Slot;
import com.beemdevelopment.aegis.db.slots.SlotException;
import com.beemdevelopment.aegis.db.slots.SlotList;
import com.beemdevelopment.aegis.helpers.FingerprintHelper;
import com.beemdevelopment.aegis.helpers.BiometricSlotInitializer;
import com.beemdevelopment.aegis.helpers.BiometricsHelper;
import com.beemdevelopment.aegis.ui.views.SlotAdapter;
import javax.crypto.Cipher;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Listener, Dialogs.SlotListener {
public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Listener {
private DatabaseFileCredentials _creds;
private SlotAdapter _adapter;
@ -42,14 +45,19 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
bar.setHomeAsUpIndicator(R.drawable.ic_close);
bar.setDisplayHomeAsUpEnabled(true);
findViewById(R.id.button_add_fingerprint).setOnClickListener(view -> {
if (FingerprintHelper.isSupported() && FingerprintHelper.isAvailable(this)) {
Dialogs.showFingerprintDialog(this ,this);
findViewById(R.id.button_add_biometric).setOnClickListener(view -> {
if (BiometricsHelper.isAvailable(this)) {
BiometricSlotInitializer initializer = new BiometricSlotInitializer(SlotManagerActivity.this, new RegisterBiometricsListener());
BiometricPrompt.PromptInfo info = new BiometricPrompt.PromptInfo.Builder()
.setTitle(getString(R.string.add_biometric_slot))
.setNegativeButtonText(getString(android.R.string.cancel))
.build();
initializer.authenticate(info);
}
});
findViewById(R.id.button_add_password).setOnClickListener(view -> {
Dialogs.showSetPasswordDialog(this, this);
Dialogs.showSetPasswordDialog(this, new PasswordListener());
});
// set up the recycler view
@ -66,17 +74,17 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
_adapter.addSlot(slot);
}
updateFingerprintButton();
updateBiometricsButton();
}
private void updateFingerprintButton() {
// only show the fingerprint option if we can get an instance of the fingerprint manager
private void updateBiometricsButton() {
// only show the biometrics option if we can get an instance of the biometrics manager
// and if none of the slots in the collection has a matching alias in the keystore
int visibility = View.VISIBLE;
if (FingerprintHelper.isSupported() && FingerprintHelper.isAvailable(this)) {
if (BiometricsHelper.isAvailable(this)) {
try {
KeyStoreHandle keyStore = new KeyStoreHandle();
for (FingerprintSlot slot : _creds.getSlots().findAll(FingerprintSlot.class)) {
for (BiometricSlot slot : _creds.getSlots().findAll(BiometricSlot.class)) {
if (keyStore.containsKey(slot.getUUID().toString())) {
visibility = View.GONE;
break;
@ -88,7 +96,7 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
} else {
visibility = View.GONE;
}
findViewById(R.id.button_add_fingerprint).setVisibility(visibility);
findViewById(R.id.button_add_biometric).setVisibility(visibility);
}
private void onSave() {
@ -164,29 +172,60 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
slots.remove(slot);
_adapter.removeSlot(slot);
_edited = true;
updateFingerprintButton();
updateBiometricsButton();
})
.setNegativeButton(android.R.string.no, null)
.create());
}
@Override
public void onSlotResult(Slot slot, Cipher cipher) {
try {
slot.setKey(_creds.getKey(), cipher);
} catch (SlotException e) {
onException(e);
return;
}
private void addSlot(Slot slot) {
_creds.getSlots().add(slot);
_adapter.addSlot(slot);
_edited = true;
updateFingerprintButton();
updateBiometricsButton();
}
@Override
public void onException(Exception e) {
Toast.makeText(this, getString(R.string.adding_new_slot_error) + e.getMessage(), Toast.LENGTH_SHORT).show();
private void showSlotError(String error) {
Toast.makeText(SlotManagerActivity.this, getString(R.string.adding_new_slot_error) + error, Toast.LENGTH_SHORT).show();
}
private class RegisterBiometricsListener implements BiometricSlotInitializer.Listener {
@Override
public void onInitializeSlot(BiometricSlot slot, Cipher cipher) {
try {
slot.setKey(_creds.getKey(), cipher);
addSlot(slot);
} catch (SlotException e) {
onSlotInitializationFailed(0, e.toString());
}
}
@Override
public void onSlotInitializationFailed(int errorCode, @NonNull CharSequence errString) {
if (!BiometricsHelper.isCanceled(errorCode)) {
showSlotError(errString.toString());
}
}
}
private class PasswordListener implements Dialogs.SlotListener {
@Override
public void onSlotResult(Slot slot, Cipher cipher) {
try {
slot.setKey(_creds.getKey(), cipher);
} catch (SlotException e) {
onException(e);
return;
}
addSlot(slot);
}
@Override
public void onException(Exception e) {
showSlotError(e.toString());
}
}
}

View file

@ -1,53 +1,39 @@
package com.beemdevelopment.aegis.ui.slides;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
import android.os.Bundle;
import android.text.method.PasswordTransformationMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.biometric.BiometricPrompt;
import androidx.fragment.app.Fragment;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
import com.beemdevelopment.aegis.db.slots.Slot;
import com.beemdevelopment.aegis.db.slots.BiometricSlot;
import com.beemdevelopment.aegis.helpers.BiometricSlotInitializer;
import com.beemdevelopment.aegis.helpers.BiometricsHelper;
import com.beemdevelopment.aegis.helpers.EditTextHelper;
import com.beemdevelopment.aegis.helpers.FingerprintUiHelper;
import com.github.paolorotolo.appintro.ISlidePolicy;
import com.github.paolorotolo.appintro.ISlideSelectionListener;
import com.google.android.material.snackbar.Snackbar;
import com.mattprecious.swirl.SwirlView;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import androidx.fragment.app.Fragment;
public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiHelper.Callback, ISlidePolicy, ISlideSelectionListener {
public class CustomAuthenticatedSlide extends Fragment implements ISlidePolicy, ISlideSelectionListener {
private int _cryptType;
private EditText _textPassword;
private EditText _textPasswordConfirm;
private CheckBox _checkPasswordVisibility;
private int _bgColor;
private LinearLayout _boxFingerprint;
private SwirlView _imgFingerprint;
private TextView _textFingerprint;
private FingerprintUiHelper _fingerHelper;
private KeyStoreHandle _storeHandle;
private FingerprintSlot _fingerSlot;
private FingerprintManager.CryptoObject _fingerCryptoObj;
private boolean _fingerAuthenticated;
private BiometricSlot _bioSlot;
private Cipher _bioCipher;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@ -55,13 +41,6 @@ public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiH
_textPassword = view.findViewById(R.id.text_password);
_textPasswordConfirm = view.findViewById(R.id.text_password_confirm);
_checkPasswordVisibility = view.findViewById(R.id.check_toggle_visibility);
_boxFingerprint = view.findViewById(R.id.box_fingerprint);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
ViewGroup insertPoint = view.findViewById(R.id.img_fingerprint_insert);
_imgFingerprint = new SwirlView(getContext());
insertPoint.addView(_imgFingerprint, 0, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
}
_checkPasswordVisibility.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (isChecked) {
@ -74,7 +53,6 @@ public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiH
}
});
_textFingerprint = view.findViewById(R.id.text_fingerprint);
view.findViewById(R.id.main).setBackgroundColor(_bgColor);
return view;
}
@ -83,82 +61,53 @@ public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiH
return _cryptType;
}
public BiometricSlot getBiometricSlot() {
return _bioSlot;
}
public Cipher getBiometriCipher() {
return _bioCipher;
}
public char[] getPassword() {
return EditTextHelper.getEditTextChars(_textPassword);
}
@SuppressLint("NewApi")
public Cipher getFingerCipher() {
return _fingerCryptoObj.getCipher();
}
public FingerprintSlot getFingerSlot() {
return _fingerSlot;
}
public void setBgColor(int color) {
_bgColor = color;
}
public void showBiometricPrompt() {
BiometricSlotInitializer initializer = new BiometricSlotInitializer(this, new BiometricsListener());
BiometricPrompt.PromptInfo info = new BiometricPrompt.PromptInfo.Builder()
.setTitle(getString(R.string.set_up_biometric))
.setNegativeButtonText(getString(android.R.string.cancel))
.build();
initializer.authenticate(info);
}
@Override
@SuppressLint("NewApi")
public void onSlideSelected() {
Intent intent = getActivity().getIntent();
_cryptType = intent.getIntExtra("cryptType", CustomAuthenticationSlide.CRYPT_TYPE_INVALID);
switch(_cryptType) {
case CustomAuthenticationSlide.CRYPT_TYPE_NONE:
case CustomAuthenticationSlide.CRYPT_TYPE_PASS:
break;
case CustomAuthenticationSlide.CRYPT_TYPE_FINGER:
_boxFingerprint.setVisibility(View.VISIBLE);
SecretKey key;
try {
if (_storeHandle == null) {
_storeHandle = new KeyStoreHandle();
_fingerSlot = new FingerprintSlot();
}
key = _storeHandle.generateKey(_fingerSlot.getUUID().toString());
} catch (KeyStoreHandleException e) {
throw new RuntimeException(e);
}
if (_fingerHelper == null) {
FingerprintManager fingerManager = (FingerprintManager) getContext().getSystemService(Context.FINGERPRINT_SERVICE);
_fingerHelper = new FingerprintUiHelper(fingerManager, _imgFingerprint, _textFingerprint, this);
}
try {
Cipher cipher = Slot.createEncryptCipher(key);
_fingerCryptoObj = new FingerprintManager.CryptoObject(cipher);
} catch (Exception e) {
throw new RuntimeException(e);
}
_fingerHelper.startListening(_fingerCryptoObj);
break;
default:
throw new RuntimeException();
if (_cryptType == CustomAuthenticationSlide.CRYPT_TYPE_BIOMETRIC) {
showBiometricPrompt();
}
}
@Override
@SuppressLint("NewApi")
public void onSlideDeselected() {
if (_fingerHelper != null) {
_fingerAuthenticated = false;
_boxFingerprint.setVisibility(View.INVISIBLE);
_fingerHelper.stopListening();
}
}
@Override
public boolean isPolicyRespected() {
switch(_cryptType) {
switch (_cryptType) {
case CustomAuthenticationSlide.CRYPT_TYPE_NONE:
return true;
case CustomAuthenticationSlide.CRYPT_TYPE_FINGER:
if (!_fingerAuthenticated) {
case CustomAuthenticationSlide.CRYPT_TYPE_BIOMETRIC:
if (_bioSlot == null) {
return false;
}
// intentional fallthrough
@ -169,7 +118,7 @@ public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiH
return false;
default:
throw new RuntimeException();
return false;
}
}
@ -178,26 +127,30 @@ public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiH
String message;
if (!EditTextHelper.areEditTextsEqual(_textPassword, _textPasswordConfirm) && !_checkPasswordVisibility.isChecked()) {
message = getString(R.string.password_equality_error);
} else if (!_fingerAuthenticated) {
message = getString(R.string.register_fingerprint);
} else {
return;
}
View view = getView();
if (view != null) {
Snackbar snackbar = Snackbar.make(view, message, Snackbar.LENGTH_LONG);
snackbar.show();
View view = getView();
if (view != null) {
Snackbar snackbar = Snackbar.make(view, message, Snackbar.LENGTH_LONG);
snackbar.show();
}
} else if (_bioSlot == null) {
showBiometricPrompt();
}
}
@Override
public void onAuthenticated() {
_fingerAuthenticated = true;
}
private class BiometricsListener implements BiometricSlotInitializer.Listener {
@Override
public void onError() {
@Override
public void onInitializeSlot(BiometricSlot slot, Cipher cipher) {
_bioSlot = slot;
_bioCipher = cipher;
}
@Override
public void onSlotInitializationFailed(int errorCode, @NonNull CharSequence errString) {
if (!BiometricsHelper.isCanceled(errorCode)) {
Toast.makeText(CustomAuthenticatedSlide.this.getContext(), errString, Toast.LENGTH_LONG).show();
}
}
}
}

View file

@ -1,8 +1,6 @@
package com.beemdevelopment.aegis.ui.slides;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@ -11,18 +9,18 @@ import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.TextView;
import androidx.fragment.app.Fragment;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.helpers.FingerprintHelper;
import com.beemdevelopment.aegis.helpers.BiometricsHelper;
import com.github.paolorotolo.appintro.ISlidePolicy;
import com.google.android.material.snackbar.Snackbar;
import androidx.fragment.app.Fragment;
public class CustomAuthenticationSlide extends Fragment implements ISlidePolicy, RadioGroup.OnCheckedChangeListener {
public static final int CRYPT_TYPE_INVALID = 0;
public static final int CRYPT_TYPE_NONE = 1;
public static final int CRYPT_TYPE_PASS = 2;
public static final int CRYPT_TYPE_FINGER = 3;
public static final int CRYPT_TYPE_BIOMETRIC = 3;
private RadioGroup _buttonGroup;
private int _bgColor;
@ -35,9 +33,9 @@ public class CustomAuthenticationSlide extends Fragment implements ISlidePolicy,
onCheckedChanged(_buttonGroup, _buttonGroup.getCheckedRadioButtonId());
// only enable the fingerprint option if the api version is new enough, permission is granted and a scanner is found
if (FingerprintHelper.isSupported() && FingerprintHelper.isAvailable(getContext())) {
RadioButton button = view.findViewById(R.id.rb_fingerprint);
TextView text = view.findViewById(R.id.text_rb_fingerprint);
if (BiometricsHelper.isAvailable(getContext())) {
RadioButton button = view.findViewById(R.id.rb_biometrics);
TextView text = view.findViewById(R.id.text_rb_biometrics);
button.setEnabled(true);
text.setEnabled(true);
}
@ -75,8 +73,8 @@ public class CustomAuthenticationSlide extends Fragment implements ISlidePolicy,
case R.id.rb_password:
id = CRYPT_TYPE_PASS;
break;
case R.id.rb_fingerprint:
id = CRYPT_TYPE_FINGER;
case R.id.rb_biometrics:
id = CRYPT_TYPE_BIOMETRIC;
break;
default:
throw new RuntimeException();

View file

@ -6,7 +6,7 @@ import android.content.Context;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.crypto.CryptoUtils;
import com.beemdevelopment.aegis.crypto.MasterKey;
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
import com.beemdevelopment.aegis.db.slots.BiometricSlot;
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
import com.beemdevelopment.aegis.db.slots.Slot;
import com.beemdevelopment.aegis.db.slots.SlotException;
@ -38,8 +38,8 @@ public class SlotListTask<T extends Slot> extends ProgressDialogTask<SlotListTas
if (slot instanceof PasswordSlot) {
char[] password = (char[]) params.getObj();
return decryptPasswordSlot((PasswordSlot) slot, password);
} else if (slot instanceof FingerprintSlot) {
return decryptFingerprintSlot((FingerprintSlot) slot, (Cipher) params.getObj());
} else if (slot instanceof BiometricSlot) {
return decryptBiometricSlot((BiometricSlot) slot, (Cipher) params.getObj());
}
} catch (SlotException e) {
throw new RuntimeException(e);
@ -51,7 +51,7 @@ public class SlotListTask<T extends Slot> extends ProgressDialogTask<SlotListTas
return null;
}
private Result decryptFingerprintSlot(FingerprintSlot slot, Cipher cipher)
private Result decryptBiometricSlot(BiometricSlot slot, Cipher cipher)
throws SlotException, SlotIntegrityException {
MasterKey key = slot.getKey(cipher);
return new Result(key, slot);

View file

@ -5,16 +5,16 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
import com.beemdevelopment.aegis.db.slots.BiometricSlot;
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
import com.beemdevelopment.aegis.db.slots.RawSlot;
import com.beemdevelopment.aegis.db.slots.Slot;
import com.beemdevelopment.aegis.helpers.FingerprintHelper;
import androidx.recyclerview.widget.RecyclerView;
import com.beemdevelopment.aegis.helpers.BiometricsHelper;
public class SlotHolder extends RecyclerView.ViewHolder {
private TextView _slotUsed;
@ -36,10 +36,10 @@ public class SlotHolder extends RecyclerView.ViewHolder {
if (slot instanceof PasswordSlot) {
_slotName.setText(R.string.password);
_slotImg.setImageResource(R.drawable.ic_create_black_24dp);
} else if (slot instanceof FingerprintSlot) {
_slotName.setText(R.string.authentication_method_fingerprint);
} else if (slot instanceof BiometricSlot) {
_slotName.setText(R.string.authentication_method_biometrics);
_slotImg.setImageResource(R.drawable.ic_fingerprint_black_24dp);
if (FingerprintHelper.isSupported()) {
if (BiometricsHelper.isAvailable(itemView.getContext())) {
try {
KeyStoreHandle keyStore = new KeyStoreHandle();
if (keyStore.containsKey(slot.getUUID().toString())) {