Better fingerprint error management #38 #52

This commit is contained in:
J-Jamet
2018-03-23 13:48:51 +01:00
parent 33404add38
commit 50bf22a4c7
6 changed files with 129 additions and 100 deletions

View File

@@ -30,6 +30,7 @@ import android.support.annotation.RequiresApi;
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
import android.support.v4.os.CancellationSignal;
import android.util.Base64;
import android.util.Log;
import java.io.IOException;
import java.security.KeyStore;
@@ -47,7 +48,9 @@ import javax.crypto.spec.IvParameterSpec;
@RequiresApi(api = Build.VERSION_CODES.M)
public class FingerPrintHelper {
private static final String FINGERPRINT_KEYSTORE_KEY = "example-key";
private static final String TAG = FingerPrintHelper.class.getName();
private static final String FINGERPRINT_KEYSTORE_KEY = "com.kunzisoft.keepass.fingerprint.key";
private FingerprintManagerCompat fingerprintManager;
private KeyStore keyStore = null;
@@ -65,8 +68,7 @@ public class FingerPrintHelper {
this.authenticationCallback = authenticationCallback;
}
public void startListening() {
public synchronized void startListening() {
// starts listening for fingerprints with the initialised crypto object
cancellationSignal = new CancellationSignal();
fingerprintManager.authenticate(
@@ -77,7 +79,7 @@ public class FingerPrintHelper {
null);
}
public void stopListening() {
public synchronized void stopListening() {
if (!isFingerprintInitialized(false)) {
return;
}
@@ -113,6 +115,7 @@ public class FingerPrintHelper {
this.cryptoObject = new FingerprintManagerCompat.CryptoObject(cipher);
setInitOk(true);
} catch (final Exception e) {
Log.e(TAG, "Unable to initialize the keystore", e);
setInitOk(false);
fingerPrintCallback.onFingerPrintException(e);
}
@@ -142,6 +145,8 @@ public class FingerPrintHelper {
return;
}
try {
stopListening();
createNewKeyIfNeeded(false); // no need to keep deleting existing keys
keyStore.load(null);
final SecretKey key = (SecretKey) keyStore.getKey(FINGERPRINT_KEYSTORE_KEY, null);
@@ -149,10 +154,13 @@ public class FingerPrintHelper {
startListening();
} catch (final UnrecoverableKeyException unrecoverableKeyException) {
Log.e(TAG, "Unable to initialize encrypt data", unrecoverableKeyException);
deleteEntryKey();
} catch (final KeyPermanentlyInvalidatedException invalidKeyException) {
Log.e(TAG, "Unable to initialize encrypt data", invalidKeyException);
fingerPrintCallback.onInvalidKeyException(invalidKeyException);
} catch (final Exception e) {
Log.e(TAG, "Unable to initialize encrypt data", e);
fingerPrintCallback.onFingerPrintException(e);
}
}
@@ -164,14 +172,15 @@ public class FingerPrintHelper {
try {
// actual do encryption here
byte[] encrypted = cipher.doFinal(value.getBytes());
final String encryptedValue = Base64.encodeToString(encrypted, Base64.DEFAULT);
final String encryptedValue = Base64.encodeToString(encrypted, Base64.NO_WRAP);
// passes updated iv spec on to callback so this can be stored for decryption
final IvParameterSpec spec = cipher.getParameters().getParameterSpec(IvParameterSpec.class);
final String ivSpecValue = Base64.encodeToString(spec.getIV(), Base64.DEFAULT);
final String ivSpecValue = Base64.encodeToString(spec.getIV(), Base64.NO_WRAP);
fingerPrintCallback.handleEncryptedResult(encryptedValue, ivSpecValue);
} catch (final Exception e) {
Log.e(TAG, "Unable to encrypt data", e);
fingerPrintCallback.onFingerPrintException(e);
}
}
@@ -181,21 +190,26 @@ public class FingerPrintHelper {
return;
}
try {
stopListening();
createNewKeyIfNeeded(false);
keyStore.load(null);
final SecretKey key = (SecretKey) keyStore.getKey(FINGERPRINT_KEYSTORE_KEY, null);
// important to restore spec here that was used for decryption
final byte[] iv = Base64.decode(ivSpecValue, Base64.DEFAULT);
final byte[] iv = Base64.decode(ivSpecValue, Base64.NO_WRAP);
final IvParameterSpec spec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, key, spec);
startListening();
} catch (final KeyPermanentlyInvalidatedException invalidKeyException) {
fingerPrintCallback.onInvalidKeyException(invalidKeyException);
} catch (final UnrecoverableKeyException unrecoverableKeyException) {
Log.e(TAG, "Unable to initialize decrypt data", unrecoverableKeyException);
deleteEntryKey();
} catch (final KeyPermanentlyInvalidatedException invalidKeyException) {
Log.e(TAG, "Unable to initialize decrypt data", invalidKeyException);
fingerPrintCallback.onInvalidKeyException(invalidKeyException);
} catch (final Exception e) {
Log.e(TAG, "Unable to initialize decrypt data", e);
fingerPrintCallback.onFingerPrintException(e);
}
}
@@ -206,15 +220,17 @@ public class FingerPrintHelper {
}
try {
// actual decryption here
final byte[] encrypted = Base64.decode(encryptedValue, Base64.DEFAULT);
final byte[] encrypted = Base64.decode(encryptedValue, Base64.NO_WRAP);
byte[] decrypted = cipher.doFinal(encrypted);
final String decryptedString = new String(decrypted);
//final String encryptedString = Base64.encodeToString(encrypted, 0 /* flags */);
fingerPrintCallback.handleDecryptedResult(decryptedString);
} catch (final BadPaddingException badPaddingException) {
Log.e(TAG, "Unable to decrypt data", badPaddingException);
fingerPrintCallback.onInvalidKeyException(badPaddingException);
} catch (final Exception e) {
Log.e(TAG, "Unable to decrypt data", e);
fingerPrintCallback.onFingerPrintException(e);
}
}
@@ -249,6 +265,7 @@ public class FingerPrintHelper {
keyGenerator.generateKey();
}
} catch (final Exception e) {
Log.e(TAG, "Unable to create a key in keystore", e);
fingerPrintCallback.onFingerPrintException(e);
}
}
@@ -262,6 +279,7 @@ public class FingerPrintHelper {
| NoSuchAlgorithmException
| IOException
| NullPointerException e) {
Log.e(TAG, "Unable to delete entry key in keystore", e);
if (fingerPrintCallback != null)
fingerPrintCallback.onFingerPrintException(e);
}

View File

@@ -89,6 +89,8 @@ import static com.keepassdroid.fingerprint.FingerPrintHelper.Mode.STORE_MODE;
public class PasswordActivity extends StylishActivity
implements FingerPrintHelper.FingerPrintCallback, UriIntentInitTaskCallback {
private static final String TAG = PasswordActivity.class.getName();
public static final String KEY_DEFAULT_FILENAME = "defaultFileName";
private static final String KEY_PASSWORD = "password";
@@ -440,7 +442,7 @@ public class PasswordActivity extends StylishActivity
if ( !fingerprintMustBeConfigured ) {
final boolean validInput = s.length() > 0;
// encrypt or decrypt mode based on how much input or not
setFingerPrintTextView(validInput ? R.string.store_with_fingerprint : R.string.scanning_fingerprint);
setFingerPrintView(validInput ? R.string.store_with_fingerprint : R.string.scanning_fingerprint);
if (validInput)
toggleFingerprintMode(STORE_MODE);
else
@@ -455,22 +457,30 @@ public class PasswordActivity extends StylishActivity
public void onAuthenticationError(
final int errorCode,
final CharSequence errString) {
Log.i(getClass().getName(), errString.toString());
switch (errorCode) {
case 5:
Log.i(TAG, "Fingerprint authentication error. Code : " + errorCode + " Error : " + errString);
break;
default:
Log.e(TAG, "Fingerprint authentication error. Code : " + errorCode + " Error : " + errString);
setFingerPrintView(errString.toString(), true);
}
}
@Override
public void onAuthenticationHelp(
final int helpCode,
final CharSequence helpString) {
Log.w(TAG, "Fingerprint authentication help. Code : " + helpCode + " Help : " + helpString);
showError(helpString);
reInitWithSameFingerprintMode();
setFingerPrintView(helpString.toString(), true);
fingerprintTextView.setText(helpString);
}
@Override
public void onAuthenticationFailed() {
Log.e(TAG, "Fingerprint authentication failed, fingerprint not recognized");
showError(R.string.fingerprint_not_recognized);
reInitWithSameFingerprintMode();
}
@Override
@@ -504,7 +514,7 @@ public class PasswordActivity extends StylishActivity
@RequiresApi(api = Build.VERSION_CODES.M)
private void initEncryptData() {
setFingerPrintTextView(R.string.store_with_fingerprint);
setFingerPrintView(R.string.store_with_fingerprint);
fingerPrintMode = STORE_MODE;
if (fingerPrintHelper != null)
fingerPrintHelper.initEncryptData();
@@ -512,7 +522,7 @@ public class PasswordActivity extends StylishActivity
@RequiresApi(api = Build.VERSION_CODES.M)
private void initDecryptData() {
setFingerPrintTextView(R.string.scanning_fingerprint);
setFingerPrintView(R.string.scanning_fingerprint);
fingerPrintMode = OPEN_MODE;
if (fingerPrintHelper != null) {
final String ivSpecValue = prefsNoBackup.getString(getPreferenceKeyIvSpec(), null);
@@ -525,12 +535,12 @@ public class PasswordActivity extends StylishActivity
private synchronized void toggleFingerprintMode(final FingerPrintHelper.Mode newMode) {
if( !newMode.equals(fingerPrintMode) ) {
fingerPrintMode = newMode;
reInitWithSameFingerprintMode();
reInitWithFingerprintMode();
}
}
@RequiresApi(api = Build.VERSION_CODES.M)
private synchronized void reInitWithSameFingerprintMode() {
private synchronized void reInitWithFingerprintMode() {
switch (fingerPrintMode) {
case STORE_MODE:
initEncryptData();
@@ -545,8 +555,6 @@ public class PasswordActivity extends StylishActivity
@Override
protected void onPause() {
super.onPause();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (fingerPrintAnimatedVector != null) {
fingerPrintAnimatedVector.stopScan();
@@ -557,18 +565,33 @@ public class PasswordActivity extends StylishActivity
fingerPrintHelper.stopListening();
}
}
super.onPause();
}
private void setFingerPrintVisibility(final int vis) {
runOnUiThread(() -> fingerprintContainerView.setVisibility(vis));
}
private void setFingerPrintTextView(final int textId) {
runOnUiThread(() -> fingerprintTextView.setText(textId));
private void setFingerPrintView(final int textId) {
setFingerPrintView(textId, false);
}
private void setFingerPrintAlphaImageView(final float alpha) {
runOnUiThread(() -> fingerprintContainerView.setAlpha(alpha));
private void setFingerPrintView(final CharSequence text) {
setFingerPrintView(text, false);
}
private void setFingerPrintView(final int textId, boolean lock) {
setFingerPrintView(getString(textId), lock);
}
private void setFingerPrintView(final CharSequence text, boolean lock) {
runOnUiThread(() -> {
if (lock) {
fingerprintContainerView.setAlpha(0.6f);
} else
fingerprintContainerView.setAlpha(1f);
fingerprintTextView.setText(text);
});
}
@RequiresApi(api = Build.VERSION_CODES.M)
@@ -589,18 +612,16 @@ public class PasswordActivity extends StylishActivity
setFingerPrintVisibility(View.VISIBLE);
if (!fingerPrintHelper.hasEnrolledFingerprints()) {
setFingerPrintAlphaImageView(0.6f);
// This happens when no fingerprints are registered. Listening won't start
setFingerPrintTextView(R.string.configure_fingerprint);
setFingerPrintView(R.string.configure_fingerprint, true);
}
// finally fingerprint available and configured so we can use it
else {
fingerprintMustBeConfigured = false;
setFingerPrintAlphaImageView(1f);
// fingerprint available but no stored password found yet for this DB so show info don't listen
if (!prefsNoBackup.contains(getPreferenceKeyValue())) {
setFingerPrintTextView(R.string.no_password_stored);
setFingerPrintView(R.string.no_password_stored);
// listen for encryption
initEncryptData();
}
@@ -616,7 +637,7 @@ public class PasswordActivity extends StylishActivity
invalidateOptionsMenu();
}
private void removePrefsNoBackupKeys() {
private void removePrefsNoBackupKey() {
prefsNoBackup.edit()
.remove(getPreferenceKeyValue())
.remove(getPreferenceKeyIvSpec())
@@ -632,7 +653,7 @@ public class PasswordActivity extends StylishActivity
.putString(getPreferenceKeyIvSpec(), ivSpec)
.apply();
verifyAllViewsAndLoadDatabase();
setFingerPrintTextView(R.string.encrypted_value_stored);
setFingerPrintView(R.string.encrypted_value_stored);
}
@Override
@@ -644,22 +665,27 @@ public class PasswordActivity extends StylishActivity
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
public void onInvalidKeyException(Exception e) {
showError(R.string.fingerprint_invalid_key);
removePrefsNoBackupKeys();
e.printStackTrace();
reInitWithSameFingerprintMode(); // restarts listening
showError(getString(R.string.fingerprint_invalid_key));
deleteEntryKey();
}
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
public void onFingerPrintException(Exception e) {
showError(R.string.fingerprint_error);
e.printStackTrace();
reInitWithSameFingerprintMode();
showError(getString(R.string.fingerprint_error, e.getMessage()));
setFingerPrintView(e.getLocalizedMessage(), true);
}
@RequiresApi(api = Build.VERSION_CODES.M)
private void deleteEntryKey() {
fingerPrintHelper.deleteEntryKey();
removePrefsNoBackupKey();
fingerPrintMode = NOT_CONFIGURED_MODE;
checkFingerprintAvailability();
}
private void showError(final int messageId) {
runOnUiThread(() -> Toast.makeText(getApplicationContext(), messageId, Toast.LENGTH_SHORT).show());
showError(getString(messageId));
}
private void showError(final CharSequence message) {
@@ -753,10 +779,7 @@ public class PasswordActivity extends StylishActivity
break;
case R.id.menu_fingerprint_remove_key:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
fingerPrintHelper.deleteEntryKey();
removePrefsNoBackupKeys();
fingerPrintMode = NOT_CONFIGURED_MODE;
checkFingerprintAvailability();
deleteEntryKey();
}
break;
default:
@@ -793,11 +816,8 @@ public class PasswordActivity extends StylishActivity
// Recheck fingerprint if error
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//setEmptyViews();
//mMessage = getString(R.string.fingerprint_error) + " : " + mMessage;
// TODO Change fingerprint message
// Stay with the same mode
reInitWithSameFingerprintMode();
reInitWithFingerprintMode();
}
if (db.passwordEncodingError) {

View File

@@ -131,16 +131,13 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
if (!fingerprintSupported) {
// False if under Marshmallow
fingerprintEnablePreference.setChecked(false);
fingerprintEnablePreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
fingerprintEnablePreference.setOnPreferenceClickListener(preference -> {
FragmentManager fragmentManager = getFragmentManager();
assert fragmentManager != null;
((SwitchPreference) preference).setChecked(false);
UnavailableFeatureDialogFragment.getInstance(Build.VERSION_CODES.M)
.show(getFragmentManager(), "unavailableFeatureDialog");
return false;
}
});
}
@@ -148,9 +145,7 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
if (!fingerprintSupported) {
deleteKeysFingerprints.setEnabled(false);
} else {
deleteKeysFingerprints.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
deleteKeysFingerprints.setOnPreferenceClickListener(preference -> {
new AlertDialog.Builder(getContext())
.setMessage(getResources().getString(R.string.fingerprint_delete_all_warning))
.setIcon(getResources().getDrawable(
@@ -166,12 +161,13 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
getContext(),
new FingerPrintHelper.FingerPrintErrorCallback() {
@Override
public void onInvalidKeyException(Exception e) {
}
public void onInvalidKeyException(Exception e) {}
@Override
public void onFingerPrintException(Exception e) {
Toast.makeText(getContext(), R.string.fingerprint_error, Toast.LENGTH_SHORT).show();
Toast.makeText(getContext(),
getString(R.string.fingerprint_error, e.getLocalizedMessage()),
Toast.LENGTH_SHORT).show();
}
});
PreferencesUtil.deleteAllValuesFromNoBackupPreferences(getContext());
@@ -179,14 +175,9 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
})
.setNegativeButton(
getResources().getString(android.R.string.no),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
}
(dialog, which) -> {
}).show();
return false;
}
});
}
break;

View File

@@ -188,9 +188,9 @@
<string name="configure_fingerprint">Reconnaissance d\'empreinte digitale non configuré pour cet appareil</string>
<string name="scanning_fingerprint">Attente d\'une reconnaissance d\'empreinte digitale</string>
<string name="encrypted_value_stored">Mot de passe encrypté stocké</string>
<string name="fingerprint_invalid_key">Problème de clé invalide</string>
<string name="fingerprint_invalid_key">Problème de clé invalide. Restaurez votre mot de passe.</string>
<string name="fingerprint_not_recognized">Empreinte digitale non reconnue</string>
<string name="fingerprint_error">Problème d\'empreinte digitale</string>
<string name="fingerprint_error">Problème d\'empreinte digitale : %1$s</string>
<string name="store_with_fingerprint">Utiliser l\'empreinte digitale pour stocker le mot de passe</string>
<string name="no_password_stored">Pas de mot de passe encore stocké pour cette base de données</string>
<string name="history">Historique</string>

View File

@@ -161,8 +161,8 @@
<string name="configure_fingerprint">Az ujjlenyomat használat nincs még beállítva az eszközön</string>
<string name="scanning_fingerprint">Várakozás az ujjelnyomat megadására</string>
<string name="encrypted_value_stored">Titkosított jelszó elmentve</string>
<string name="fingerprint_invalid_key">Érvénytelen kulcs</string>
<string name="fingerprint_error">Érvénytelen ujjlenyomat</string>
<string name="fingerprint_invalid_key">Érvénytelen kulcs. Helyezze vissza jelszavát.</string>
<string name="fingerprint_error">Érvénytelen ujjlenyomat : %1$s</string>
<string name="store_with_fingerprint">Használjon ujjlenyomatot a jelszó elmentéséhez</string>
<string name="no_password_stored">Nincs még jelszó beállítva ehhez az adatbázishoz</string>

View File

@@ -188,9 +188,9 @@
<string name="configure_fingerprint">Fingerprint supported but not configured for device</string>
<string name="scanning_fingerprint">Listening for fingerprints</string>
<string name="encrypted_value_stored">Encrypted password stored</string>
<string name="fingerprint_invalid_key">Invalid Key problem</string>
<string name="fingerprint_invalid_key">Invalid fingerprint key problem. Restore your password.</string>
<string name="fingerprint_not_recognized">Fingerprint not recognized</string>
<string name="fingerprint_error">Fingerprint problem</string>
<string name="fingerprint_error">Fingerprint problem : %1$s</string>
<string name="store_with_fingerprint">Use fingerprint to store this password</string>
<string name="no_password_stored">No password stored yet for this database</string>
<string name="history">History</string>