mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Merge remote-tracking branch 'hanscappelle/feature/fingerprint-password-handling' into feature/fingerprint-password-handling
This commit is contained in:
51
.gitignore
vendored
51
.gitignore
vendored
@@ -1,8 +1,51 @@
|
|||||||
.gradle
|
|
||||||
/local.properties
|
|
||||||
/.idea/workspace.xml
|
|
||||||
/.idea/libraries
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/build
|
/build
|
||||||
/app/build
|
/app/build
|
||||||
/projectFilesBackup
|
/projectFilesBackup
|
||||||
|
|
||||||
|
# Built application files
|
||||||
|
*.apk
|
||||||
|
*.ap_
|
||||||
|
|
||||||
|
# Files for the ART/Dalvik VM
|
||||||
|
*.dex
|
||||||
|
|
||||||
|
# Java class files
|
||||||
|
*.class
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
bin/
|
||||||
|
gen/
|
||||||
|
out/
|
||||||
|
|
||||||
|
# Gradle files
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Local configuration file (sdk path, etc)
|
||||||
|
local.properties
|
||||||
|
|
||||||
|
# Proguard folder generated by Eclipse
|
||||||
|
proguard/
|
||||||
|
|
||||||
|
# Log Files
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Android Studio Navigation editor temp files
|
||||||
|
.navigation/
|
||||||
|
|
||||||
|
# Android Studio captures folder
|
||||||
|
captures/
|
||||||
|
|
||||||
|
# Intellij
|
||||||
|
*.iml
|
||||||
|
.idea/*
|
||||||
|
|
||||||
|
# Keystore files
|
||||||
|
*.jks
|
||||||
|
|
||||||
|
# External native build folder generated in Android Studio 2.2 and later
|
||||||
|
.externalNativeBuild
|
||||||
|
|
||||||
|
# Google Services (e.g. APIs or Firebase)
|
||||||
|
google-services.json
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion = 22
|
compileSdkVersion = 23
|
||||||
buildToolsVersion = '26.0.2'
|
buildToolsVersion = '26.0.2'
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "com.android.keepass"
|
applicationId = "com.android.keepass"
|
||||||
minSdkVersion 3
|
minSdkVersion 4
|
||||||
targetSdkVersion 12
|
targetSdkVersion 12
|
||||||
|
|
||||||
versionCode = 155
|
versionCode = 155
|
||||||
@@ -48,4 +48,6 @@ dependencies {
|
|||||||
compile 'com.madgag.spongycastle:core:1.58.0.0'
|
compile 'com.madgag.spongycastle:core:1.58.0.0'
|
||||||
compile 'com.madgag.spongycastle:prov:1.58.0.0'
|
compile 'com.madgag.spongycastle:prov:1.58.0.0'
|
||||||
compile 'joda-time:joda-time:2.9.4'
|
compile 'joda-time:joda-time:2.9.4'
|
||||||
|
compile 'com.android.support:support-v4:23.0.0'
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,9 @@
|
|||||||
android:largeScreens="true"
|
android:largeScreens="true"
|
||||||
android:anyDensity="true"
|
android:anyDensity="true"
|
||||||
/>
|
/>
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
|
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
|
||||||
<uses-permission android:name="android.permission.VIBRATE"></uses-permission>
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||||
<application
|
<application
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:icon="@drawable/launcher"
|
android:icon="@drawable/launcher"
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import android.content.SharedPreferences;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
import android.support.v4.app.NotificationCompat;
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.method.LinkMovementMethod;
|
import android.text.method.LinkMovementMethod;
|
||||||
import android.text.method.PasswordTransformationMethod;
|
import android.text.method.PasswordTransformationMethod;
|
||||||
@@ -228,12 +229,16 @@ public class EntryActivity extends LockCloseHideActivity {
|
|||||||
|
|
||||||
private Notification getNotification(String intentText, int descResId) {
|
private Notification getNotification(String intentText, int descResId) {
|
||||||
String desc = getString(descResId);
|
String desc = getString(descResId);
|
||||||
Notification notify = new Notification(R.drawable.notify, desc, System.currentTimeMillis());
|
|
||||||
|
|
||||||
Intent intent = new Intent(intentText);
|
Intent intent = new Intent(intentText);
|
||||||
PendingIntent pending = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
|
PendingIntent pending = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||||
|
|
||||||
notify.setLatestEventInfo(this, getString(R.string.app_name), desc, pending);
|
// no longer supported for api level >22
|
||||||
|
// notify.setLatestEventInfo(this, getString(R.string.app_name), desc, pending);
|
||||||
|
// so instead using compat builder and create new notification
|
||||||
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
|
||||||
|
Notification notify = builder.setContentIntent(pending).setContentText(desc).setContentTitle(getString(R.string.app_name))
|
||||||
|
.setSmallIcon(R.drawable.notify).setTicker(desc).setWhen(System.currentTimeMillis()).build();
|
||||||
|
|
||||||
return notify;
|
return notify;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,21 +19,22 @@
|
|||||||
*/
|
*/
|
||||||
package com.keepassdroid;
|
package com.keepassdroid;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.ClipData;
|
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.DialogInterface.OnClickListener;
|
import android.content.DialogInterface.OnClickListener;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.hardware.fingerprint.FingerprintManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
import android.text.Editable;
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
|
import android.text.TextWatcher;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
@@ -58,6 +59,7 @@ import com.keepassdroid.database.edit.LoadDB;
|
|||||||
import com.keepassdroid.database.edit.OnFinish;
|
import com.keepassdroid.database.edit.OnFinish;
|
||||||
import com.keepassdroid.dialog.PasswordEncodingDialogHelper;
|
import com.keepassdroid.dialog.PasswordEncodingDialogHelper;
|
||||||
import com.keepassdroid.fileselect.BrowserDialog;
|
import com.keepassdroid.fileselect.BrowserDialog;
|
||||||
|
import com.keepassdroid.fingerprint.FingerPrintHelper;
|
||||||
import com.keepassdroid.intents.Intents;
|
import com.keepassdroid.intents.Intents;
|
||||||
import com.keepassdroid.settings.AppSettingsActivity;
|
import com.keepassdroid.settings.AppSettingsActivity;
|
||||||
import com.keepassdroid.utils.EmptyUtils;
|
import com.keepassdroid.utils.EmptyUtils;
|
||||||
@@ -68,7 +70,9 @@ import com.keepassdroid.utils.Util;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
|
|
||||||
public class PasswordActivity extends LockingActivity {
|
import javax.crypto.Cipher;
|
||||||
|
|
||||||
|
public class PasswordActivity extends LockingActivity implements FingerPrintHelper.FingerPrintCallback {
|
||||||
|
|
||||||
public static final String KEY_DEFAULT_FILENAME = "defaultFileName";
|
public static final String KEY_DEFAULT_FILENAME = "defaultFileName";
|
||||||
private static final String KEY_FILENAME = "fileName";
|
private static final String KEY_FILENAME = "fileName";
|
||||||
@@ -86,11 +90,25 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
private boolean mRememberKeyfile;
|
private boolean mRememberKeyfile;
|
||||||
SharedPreferences prefs;
|
SharedPreferences prefs;
|
||||||
|
|
||||||
public static void Launch(Activity act, String fileName) throws FileNotFoundException {
|
private FingerPrintHelper fingerPrintHelper;
|
||||||
|
private int mode;
|
||||||
|
private static final String PREF_KEY_VALUE_PREFIX = "valueFor_"; // key is a combination of db file name and this prefix
|
||||||
|
private static final String PREF_KEY_IV_PREFIX = "ivFor_"; // key is a combination of db file name and this prefix
|
||||||
|
private View fingerprintView;
|
||||||
|
private TextView confirmationView;
|
||||||
|
private EditText passwordView;
|
||||||
|
private Button confirmButton;
|
||||||
|
|
||||||
|
public static void Launch(
|
||||||
|
Activity act,
|
||||||
|
String fileName) throws FileNotFoundException {
|
||||||
Launch(act, fileName, "");
|
Launch(act, fileName, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Launch(Activity act, String fileName, String keyFile) throws FileNotFoundException {
|
public static void Launch(
|
||||||
|
Activity act,
|
||||||
|
String fileName,
|
||||||
|
String keyFile) throws FileNotFoundException {
|
||||||
if (EmptyUtils.isNullOrEmpty(fileName)) {
|
if (EmptyUtils.isNullOrEmpty(fileName)) {
|
||||||
throw new FileNotFoundException();
|
throw new FileNotFoundException();
|
||||||
}
|
}
|
||||||
@@ -114,7 +132,10 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
protected void onActivityResult(
|
||||||
|
int requestCode,
|
||||||
|
int resultCode,
|
||||||
|
Intent data) {
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
|
||||||
switch (requestCode) {
|
switch (requestCode) {
|
||||||
@@ -173,7 +194,14 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
mRememberKeyfile = prefs.getBoolean(getString(R.string.keyfile_key), getResources().getBoolean(R.bool.keyfile_default));
|
mRememberKeyfile = prefs.getBoolean(getString(R.string.keyfile_key), getResources().getBoolean(R.bool.keyfile_default));
|
||||||
setContentView(R.layout.password);
|
setContentView(R.layout.password);
|
||||||
|
|
||||||
|
confirmButton = (Button) findViewById(R.id.pass_ok);
|
||||||
|
fingerprintView = findViewById(R.id.fingerprint);
|
||||||
|
confirmationView = (TextView) findViewById(R.id.fingerprint_label);
|
||||||
|
passwordView = (EditText) findViewById(R.id.password);
|
||||||
|
|
||||||
new InitTask().execute(i);
|
new InitTask().execute(i);
|
||||||
|
|
||||||
|
initForFingerprint();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -189,6 +217,9 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
|
|
||||||
// Clear the shutdown flag
|
// Clear the shutdown flag
|
||||||
App.clearShutdown();
|
App.clearShutdown();
|
||||||
|
|
||||||
|
// checks if fingerprint is available, will also start listening for fingerprints when available
|
||||||
|
checkAvailability();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void retrieveSettings() {
|
private void retrieveSettings() {
|
||||||
@@ -223,15 +254,215 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
private void errorMessage(int resId)
|
private void errorMessage(int resId) {
|
||||||
{
|
|
||||||
Toast.makeText(this, resId, Toast.LENGTH_LONG).show();
|
Toast.makeText(this, resId, Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fingerprint related code here
|
||||||
|
|
||||||
|
private void initForFingerprint() {
|
||||||
|
fingerPrintHelper = new FingerPrintHelper(this, this);
|
||||||
|
if (fingerPrintHelper.isFingerprintSupported()) {
|
||||||
|
|
||||||
|
// when text entered we can enable the logon/purchase button and if required update encryption/decryption mode
|
||||||
|
passwordView.addTextChangedListener(new TextWatcher() {
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(
|
||||||
|
final CharSequence s,
|
||||||
|
final int start,
|
||||||
|
final int count,
|
||||||
|
final int after) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(
|
||||||
|
final CharSequence s,
|
||||||
|
final int start,
|
||||||
|
final int before,
|
||||||
|
final int count) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(final Editable s) {
|
||||||
|
final boolean validInput = s.length() > 0;
|
||||||
|
// encrypt or decrypt mode based on how much input or not
|
||||||
|
confirmationView.setText(validInput ? R.string.store_with_fingerprint : R.string.scanning_fingerprint);
|
||||||
|
mode = validInput ? toggleMode(Cipher.ENCRYPT_MODE) : toggleMode(Cipher.DECRYPT_MODE);
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// callback for fingerprint findings
|
||||||
|
fingerPrintHelper.setAuthenticationCallback(new FingerprintManager.AuthenticationCallback() {
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationError(
|
||||||
|
final int errorCode,
|
||||||
|
final CharSequence errString) {
|
||||||
|
|
||||||
|
// this is triggered on stop/start listening done by helper to switch between modes so don't restart here
|
||||||
|
// errorCode = 5
|
||||||
|
// errString = "Fingerprint operation canceled."
|
||||||
|
//onException();
|
||||||
|
//confirmationView.setText(errString);
|
||||||
|
// true false fingerprint readings are handled otherwise with the toast messages, see below in code
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationHelp(
|
||||||
|
final int helpCode,
|
||||||
|
final CharSequence helpString) {
|
||||||
|
|
||||||
|
onException();
|
||||||
|
confirmationView.setText(helpString);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationSucceeded(final FingerprintManager.AuthenticationResult result) {
|
||||||
|
|
||||||
|
if (mode == Cipher.ENCRYPT_MODE) {
|
||||||
|
|
||||||
|
// newly store the entered password in encrypted way
|
||||||
|
final String password = passwordView.getText().toString();
|
||||||
|
fingerPrintHelper.encryptData(password);
|
||||||
|
|
||||||
|
} else if (mode == Cipher.DECRYPT_MODE) {
|
||||||
|
|
||||||
|
// retrieve the encrypted value from preferences
|
||||||
|
final String encryptedValue = prefs.getString(getPreferenceKeyValue(), null);
|
||||||
|
if (encryptedValue != null) {
|
||||||
|
fingerPrintHelper.decryptData(encryptedValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationFailed() {
|
||||||
|
onException();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPreferenceKeyValue() {
|
||||||
|
// makes it possible to store passwords uniqly per database
|
||||||
|
return PREF_KEY_VALUE_PREFIX + (mDbUri != null ? mDbUri.getPath() : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPreferenceKeyIvSpec() {
|
||||||
|
return PREF_KEY_IV_PREFIX + (mDbUri != null ? mDbUri.getPath() : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private int toggleMode(final int newMode) {
|
||||||
|
// check if mode is different so we can update fingerprint helper
|
||||||
|
if (mode != newMode) {
|
||||||
|
mode = newMode;
|
||||||
|
switch (mode) {
|
||||||
|
case Cipher.ENCRYPT_MODE:
|
||||||
|
fingerPrintHelper.initEncryptData();
|
||||||
|
break;
|
||||||
|
case Cipher.DECRYPT_MODE:
|
||||||
|
final String ivSpecValue = prefs.getString(getPreferenceKeyIvSpec(), null);
|
||||||
|
fingerPrintHelper.initDecryptData(ivSpecValue);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return newMode;
|
||||||
|
}
|
||||||
|
// remains in current mode
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
// stop listening when we go in background
|
||||||
|
if (fingerPrintHelper != null) {
|
||||||
|
fingerPrintHelper.stopListening();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkAvailability() {
|
||||||
|
// fingerprint not supported (by API level or hardware) so keep option hidden
|
||||||
|
if (!fingerPrintHelper.isFingerprintSupported()) {
|
||||||
|
|
||||||
|
fingerprintView.setVisibility(View.GONE);
|
||||||
|
// since this is a fingerprint example inform user, don't do this in your app
|
||||||
|
//confirmationView.setText(R.string.fingerprint_not_supported);
|
||||||
|
confirmationView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
// fingerprint is available but not configured show icon but in disabled state with some information
|
||||||
|
else if (!fingerPrintHelper.hasEnrolledFingerprints()) {
|
||||||
|
|
||||||
|
fingerprintView.setVisibility(View.VISIBLE);
|
||||||
|
confirmationView.setVisibility(View.VISIBLE);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||||
|
fingerprintView.setAlpha(0.3f);
|
||||||
|
}
|
||||||
|
// This happens when no fingerprints are registered. Listening won't start
|
||||||
|
confirmationView.setText(R.string.configure_fingerprint);
|
||||||
|
}
|
||||||
|
// finally fingerprint available and configured so we can use it
|
||||||
|
else {
|
||||||
|
|
||||||
|
fingerprintView.setVisibility(View.VISIBLE);
|
||||||
|
confirmationView.setVisibility(View.VISIBLE);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||||
|
fingerprintView.setAlpha(1f);
|
||||||
|
}
|
||||||
|
// fingerprint available but no stored password found yet for this DB so show info don't listen
|
||||||
|
if (prefs.getString(getPreferenceKeyValue(), null) == null) {
|
||||||
|
|
||||||
|
confirmationView.setText(R.string.no_password_stored);
|
||||||
|
}
|
||||||
|
// all is set here so we can confirm to user and start listening for fingerprints
|
||||||
|
else {
|
||||||
|
|
||||||
|
confirmationView.setText(R.string.scanning_fingerprint);
|
||||||
|
// listen for decryption by default
|
||||||
|
toggleMode(Cipher.DECRYPT_MODE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleEncryptedResult(
|
||||||
|
final String value,
|
||||||
|
final String ivSpec) {
|
||||||
|
|
||||||
|
prefs.edit()
|
||||||
|
.putString(getPreferenceKeyValue(), value)
|
||||||
|
.putString(getPreferenceKeyIvSpec(), ivSpec)
|
||||||
|
.commit();
|
||||||
|
// and remove visual input to reset UI
|
||||||
|
passwordView.setText("");
|
||||||
|
confirmationView.setText(R.string.encrypted_value_stored);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleDecryptedResult(final String value) {
|
||||||
|
// on decrypt enter it for the purchase/login action
|
||||||
|
passwordView.setText(value);
|
||||||
|
confirmButton.performClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInvalidKeyException() {
|
||||||
|
Toast.makeText(this, R.string.fingerprint_invalid_key, Toast.LENGTH_SHORT).show();
|
||||||
|
checkAvailability(); // restarts listening
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException() {
|
||||||
|
Toast.makeText(this, R.string.fingerprint_error, Toast.LENGTH_SHORT).show();
|
||||||
|
checkAvailability(); // restarts listening
|
||||||
|
}
|
||||||
|
|
||||||
private class DefaultCheckChange implements CompoundButton.OnCheckedChangeListener {
|
private class DefaultCheckChange implements CompoundButton.OnCheckedChangeListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCheckedChanged(CompoundButton buttonView,
|
public void onCheckedChanged(
|
||||||
|
CompoundButton buttonView,
|
||||||
boolean isChecked) {
|
boolean isChecked) {
|
||||||
|
|
||||||
String newDefaultFileName;
|
String newDefaultFileName;
|
||||||
@@ -262,12 +493,15 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadDatabase(String pass, String keyfile) {
|
private void loadDatabase(
|
||||||
|
String pass,
|
||||||
|
String keyfile) {
|
||||||
loadDatabase(pass, UriUtil.parseDefaultFile(keyfile));
|
loadDatabase(pass, UriUtil.parseDefaultFile(keyfile));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadDatabase(String pass, Uri keyfile)
|
private void loadDatabase(
|
||||||
{
|
String pass,
|
||||||
|
Uri keyfile) {
|
||||||
if (pass.length() == 0 && (keyfile == null || keyfile.toString().length() == 0)) {
|
if (pass.length() == 0 && (keyfile == null || keyfile.toString().length() == 0)) {
|
||||||
errorMessage(R.string.error_nopass);
|
errorMessage(R.string.error_nopass);
|
||||||
return;
|
return;
|
||||||
@@ -290,7 +524,9 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
return Util.getEditText(this, resId);
|
return Util.getEditText(this, resId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setEditText(int resId, String str) {
|
private void setEditText(
|
||||||
|
int resId,
|
||||||
|
String str) {
|
||||||
TextView te = (TextView) findViewById(resId);
|
TextView te = (TextView) findViewById(resId);
|
||||||
assert (te == null);
|
assert (te == null);
|
||||||
|
|
||||||
@@ -326,9 +562,12 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final class AfterLoad extends OnFinish {
|
private final class AfterLoad extends OnFinish {
|
||||||
|
|
||||||
private Database db;
|
private Database db;
|
||||||
|
|
||||||
public AfterLoad(Handler handler, Database db) {
|
public AfterLoad(
|
||||||
|
Handler handler,
|
||||||
|
Database db) {
|
||||||
super(handler);
|
super(handler);
|
||||||
|
|
||||||
this.db = db;
|
this.db = db;
|
||||||
@@ -341,7 +580,9 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
dialog.show(PasswordActivity.this, new OnClickListener() {
|
dialog.show(PasswordActivity.this, new OnClickListener() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(
|
||||||
|
DialogInterface dialog,
|
||||||
|
int which) {
|
||||||
GroupActivity.Launch(PasswordActivity.this);
|
GroupActivity.Launch(PasswordActivity.this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,13 +596,15 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class InitTask extends AsyncTask<Intent, Void, Integer> {
|
private class InitTask extends AsyncTask<Intent, Void, Integer> {
|
||||||
|
|
||||||
String password = "";
|
String password = "";
|
||||||
boolean launch_immediately = false;
|
boolean launch_immediately = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Integer doInBackground(Intent... args) {
|
protected Integer doInBackground(Intent... args) {
|
||||||
Intent i = args[0];
|
Intent i = args[0];
|
||||||
String action = i.getAction();;
|
String action = i.getAction();
|
||||||
|
;
|
||||||
if (action != null && action.equals(VIEW_INTENT)) {
|
if (action != null && action.equals(VIEW_INTENT)) {
|
||||||
Uri incoming = i.getData();
|
Uri incoming = i.getData();
|
||||||
mDbUri = incoming;
|
mDbUri = incoming;
|
||||||
@@ -370,8 +613,7 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
|
|
||||||
if (incoming == null) {
|
if (incoming == null) {
|
||||||
return R.string.error_can_not_handle_uri;
|
return R.string.error_can_not_handle_uri;
|
||||||
}
|
} else if (incoming.getScheme().equals("file")) {
|
||||||
else if (incoming.getScheme().equals("file")) {
|
|
||||||
String fileName = incoming.getPath();
|
String fileName = incoming.getPath();
|
||||||
|
|
||||||
if (fileName.length() == 0) {
|
if (fileName.length() == 0) {
|
||||||
@@ -385,14 +627,14 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
return R.string.FileNotFound;
|
return R.string.FileNotFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mKeyUri == null)
|
if (mKeyUri == null) {
|
||||||
mKeyUri = getKeyFile(mDbUri);
|
mKeyUri = getKeyFile(mDbUri);
|
||||||
}
|
}
|
||||||
else if (incoming.getScheme().equals("content")) {
|
} else if (incoming.getScheme().equals("content")) {
|
||||||
if(mKeyUri == null)
|
if (mKeyUri == null) {
|
||||||
mKeyUri = getKeyFile(mDbUri);
|
mKeyUri = getKeyFile(mDbUri);
|
||||||
}
|
}
|
||||||
else {
|
} else {
|
||||||
return R.string.error_can_not_handle_uri;
|
return R.string.error_can_not_handle_uri;
|
||||||
}
|
}
|
||||||
password = i.getStringExtra(KEY_PASSWORD);
|
password = i.getStringExtra(KEY_PASSWORD);
|
||||||
@@ -420,14 +662,14 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
|
|
||||||
populateView();
|
populateView();
|
||||||
|
|
||||||
Button confirmButton = (Button) findViewById(R.id.pass_ok);
|
|
||||||
confirmButton.setOnClickListener(new OkClickHandler());
|
confirmButton.setOnClickListener(new OkClickHandler());
|
||||||
|
|
||||||
CheckBox checkBox = (CheckBox) findViewById(R.id.show_password);
|
CheckBox checkBox = (CheckBox) findViewById(R.id.show_password);
|
||||||
// Show or hide password
|
// Show or hide password
|
||||||
checkBox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
|
checkBox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
|
||||||
|
|
||||||
public void onCheckedChanged(CompoundButton buttonView,
|
public void onCheckedChanged(
|
||||||
|
CompoundButton buttonView,
|
||||||
boolean isChecked) {
|
boolean isChecked) {
|
||||||
TextView password = (TextView) findViewById(R.id.password);
|
TextView password = (TextView) findViewById(R.id.password);
|
||||||
|
|
||||||
@@ -457,8 +699,7 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
i.addCategory(Intent.CATEGORY_OPENABLE);
|
i.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
i.setType("*/*");
|
i.setType("*/*");
|
||||||
startActivityForResult(i, OPEN_DOC);
|
startActivityForResult(i, OPEN_DOC);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
|
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
i.addCategory(Intent.CATEGORY_OPENABLE);
|
i.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
i.setType("*/*");
|
i.setType("*/*");
|
||||||
@@ -508,8 +749,9 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
|
|
||||||
retrieveSettings();
|
retrieveSettings();
|
||||||
|
|
||||||
if (launch_immediately)
|
if (launch_immediately) {
|
||||||
loadDatabase(password, mKeyUri);
|
loadDatabase(password, mKeyUri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,16 +19,21 @@
|
|||||||
*/
|
*/
|
||||||
package com.keepassdroid.fileselect;
|
package com.keepassdroid.fileselect;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
import android.support.v4.app.ActivityCompat;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.view.ContextMenu;
|
import android.view.ContextMenu;
|
||||||
import android.view.ContextMenu.ContextMenuInfo;
|
import android.view.ContextMenu.ContextMenuInfo;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
@@ -74,6 +79,7 @@ import java.net.URLDecoder;
|
|||||||
|
|
||||||
public class FileSelectActivity extends Activity {
|
public class FileSelectActivity extends Activity {
|
||||||
|
|
||||||
|
private static final int MY_PERMISSIONS_REQUEST_EXTERNAL_STORAGE = 111;
|
||||||
private ListView mList;
|
private ListView mList;
|
||||||
private ListAdapter mAdapter;
|
private ListAdapter mAdapter;
|
||||||
|
|
||||||
@@ -218,7 +224,12 @@ public class FileSelectActivity extends Activity {
|
|||||||
startActivityForResult(i, OPEN_DOC);
|
startActivityForResult(i, OPEN_DOC);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
|
Intent i;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
|
i = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
||||||
|
} else {
|
||||||
|
i = new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
|
}
|
||||||
i.addCategory(Intent.CATEGORY_OPENABLE);
|
i.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
i.setType("*/*");
|
i.setType("*/*");
|
||||||
|
|
||||||
@@ -409,6 +420,9 @@ public class FileSelectActivity extends Activity {
|
|||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
|
// check for storage permission
|
||||||
|
checkStoragePermission();
|
||||||
|
|
||||||
// Check to see if we need to change modes
|
// Check to see if we need to change modes
|
||||||
if ( fileHistory.hasRecentFiles() != recentMode ) {
|
if ( fileHistory.hasRecentFiles() != recentMode ) {
|
||||||
// Restart the activity
|
// Restart the activity
|
||||||
@@ -421,6 +435,60 @@ public class FileSelectActivity extends Activity {
|
|||||||
fnv.updateExternalStorageWarning();
|
fnv.updateExternalStorageWarning();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkStoragePermission() {
|
||||||
|
// Here, thisActivity is the current activity
|
||||||
|
if (ContextCompat.checkSelfPermission(FileSelectActivity.this,
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||||
|
!= PackageManager.PERMISSION_GRANTED) {
|
||||||
|
|
||||||
|
// Should we show an explanation?
|
||||||
|
//if (ActivityCompat.shouldShowRequestPermissionRationale(FileSelectActivity.this,
|
||||||
|
// Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
||||||
|
|
||||||
|
// Show an explanation to the user *asynchronously* -- don't block
|
||||||
|
// this thread waiting for the user's response! After the user
|
||||||
|
// sees the explanation, try again to request the permission.
|
||||||
|
|
||||||
|
//} else {
|
||||||
|
|
||||||
|
// No explanation needed, we can request the permission.
|
||||||
|
|
||||||
|
ActivityCompat.requestPermissions(FileSelectActivity.this,
|
||||||
|
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||||
|
MY_PERMISSIONS_REQUEST_EXTERNAL_STORAGE);
|
||||||
|
|
||||||
|
// MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
|
||||||
|
// app-defined int constant. The callback method gets the
|
||||||
|
// result of the request.
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(int requestCode,
|
||||||
|
String permissions[], int[] grantResults) {
|
||||||
|
switch (requestCode) {
|
||||||
|
case MY_PERMISSIONS_REQUEST_EXTERNAL_STORAGE: {
|
||||||
|
// If request is cancelled, the result arrays are empty.
|
||||||
|
if (grantResults.length > 0
|
||||||
|
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
|
||||||
|
// permission was granted, yay! Do the
|
||||||
|
// contacts-related task you need to do.
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// permission denied, boo! Disable the
|
||||||
|
// functionality that depends on this permission.
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// other 'case' lines to check for other
|
||||||
|
// permissions this app might request
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
super.onCreateOptionsMenu(menu);
|
super.onCreateOptionsMenu(menu);
|
||||||
|
|||||||
@@ -0,0 +1,284 @@
|
|||||||
|
package com.keepassdroid.fingerprint;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.app.KeyguardManager;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.hardware.fingerprint.FingerprintManager;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.CancellationSignal;
|
||||||
|
import android.security.keystore.KeyGenParameterSpec;
|
||||||
|
import android.security.keystore.KeyPermanentlyInvalidatedException;
|
||||||
|
import android.security.keystore.KeyProperties;
|
||||||
|
import android.util.Base64;
|
||||||
|
|
||||||
|
import java.security.KeyStore;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.KeyGenerator;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
|
|
||||||
|
public class FingerPrintHelper {
|
||||||
|
|
||||||
|
private static final String ALIAS_KEY = "example-key";
|
||||||
|
|
||||||
|
private FingerprintManager fingerprintManager;
|
||||||
|
private KeyStore keyStore = null;
|
||||||
|
private KeyGenerator keyGenerator = null;
|
||||||
|
private Cipher cipher = null;
|
||||||
|
private KeyguardManager keyguardManager = null;
|
||||||
|
private FingerprintManager.CryptoObject cryptoObject = null;
|
||||||
|
|
||||||
|
private boolean initOk = false;
|
||||||
|
private FingerPrintCallback fingerPrintCallback;
|
||||||
|
private CancellationSignal cancellationSignal;
|
||||||
|
private FingerprintManager.AuthenticationCallback authenticationCallback;
|
||||||
|
|
||||||
|
public void setAuthenticationCallback(final FingerprintManager.AuthenticationCallback authenticationCallback) {
|
||||||
|
this.authenticationCallback = authenticationCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
public void startListening() {
|
||||||
|
// no need to start listening when not initialised
|
||||||
|
if (!isFingerprintInitialized()) {
|
||||||
|
if (fingerPrintCallback != null) {
|
||||||
|
fingerPrintCallback.onException();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// starts listening for fingerprints with the initialised crypto object
|
||||||
|
cancellationSignal = new CancellationSignal();
|
||||||
|
fingerprintManager.authenticate(
|
||||||
|
cryptoObject,
|
||||||
|
cancellationSignal,
|
||||||
|
0 /* flags */,
|
||||||
|
authenticationCallback,
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
public void stopListening() {
|
||||||
|
if (!isFingerprintInitialized()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cancellationSignal != null) {
|
||||||
|
cancellationSignal.cancel();
|
||||||
|
cancellationSignal = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface FingerPrintCallback {
|
||||||
|
|
||||||
|
void handleEncryptedResult(String value, String ivSpec);
|
||||||
|
|
||||||
|
void handleDecryptedResult(String value);
|
||||||
|
|
||||||
|
void onInvalidKeyException();
|
||||||
|
|
||||||
|
void onException();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.M)
|
||||||
|
public FingerPrintHelper(
|
||||||
|
final Context context,
|
||||||
|
final FingerPrintCallback fingerPrintCallback) {
|
||||||
|
|
||||||
|
if (!isFingerprintSupported()) {
|
||||||
|
// really not much to do when no fingerprint support found
|
||||||
|
setInitOk(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.fingerprintManager = context.getSystemService(FingerprintManager.class);
|
||||||
|
this.keyguardManager = context.getSystemService(KeyguardManager.class);
|
||||||
|
this.fingerPrintCallback = fingerPrintCallback;
|
||||||
|
|
||||||
|
if (hasEnrolledFingerprints()) {
|
||||||
|
try {
|
||||||
|
this.keyStore = KeyStore.getInstance("AndroidKeyStore");
|
||||||
|
this.keyGenerator = KeyGenerator.getInstance(
|
||||||
|
KeyProperties.KEY_ALGORITHM_AES,
|
||||||
|
"AndroidKeyStore");
|
||||||
|
this.cipher = Cipher.getInstance(
|
||||||
|
KeyProperties.KEY_ALGORITHM_AES + "/"
|
||||||
|
+ KeyProperties.BLOCK_MODE_CBC + "/"
|
||||||
|
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
|
||||||
|
this.cryptoObject = new FingerprintManager.CryptoObject(cipher);
|
||||||
|
setInitOk(true);
|
||||||
|
} catch (final Exception e) {
|
||||||
|
setInitOk(false);
|
||||||
|
fingerPrintCallback.onException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFingerprintInitialized() {
|
||||||
|
return hasEnrolledFingerprints() && initOk;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("NewApi")
|
||||||
|
public void initEncryptData() {
|
||||||
|
|
||||||
|
if (!isFingerprintInitialized()) {
|
||||||
|
if (fingerPrintCallback != null) {
|
||||||
|
fingerPrintCallback.onException();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
createNewKeyIfNeeded(false); // no need to keep deleting existing keys
|
||||||
|
keyStore.load(null);
|
||||||
|
final SecretKey key = (SecretKey) keyStore.getKey(ALIAS_KEY, null);
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, key);
|
||||||
|
|
||||||
|
stopListening();
|
||||||
|
startListening();
|
||||||
|
|
||||||
|
} catch (final KeyPermanentlyInvalidatedException invalidKeyException) {
|
||||||
|
fingerPrintCallback.onInvalidKeyException();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
fingerPrintCallback.onException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("NewApi")
|
||||||
|
public void encryptData(final String value) {
|
||||||
|
|
||||||
|
if (!isFingerprintInitialized()) {
|
||||||
|
if (fingerPrintCallback != null) {
|
||||||
|
fingerPrintCallback.onException();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// actual do encryption here
|
||||||
|
byte[] encrypted = cipher.doFinal(value.getBytes());
|
||||||
|
final String encryptedValue = Base64.encodeToString(encrypted, 0 /* flags */);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
fingerPrintCallback.handleEncryptedResult(encryptedValue, ivSpecValue);
|
||||||
|
|
||||||
|
} catch (final Exception e) {
|
||||||
|
fingerPrintCallback.onException();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("NewApi")
|
||||||
|
public void initDecryptData(final String ivSpecValue) {
|
||||||
|
|
||||||
|
if (!isFingerprintInitialized()) {
|
||||||
|
if (fingerPrintCallback != null) {
|
||||||
|
fingerPrintCallback.onException();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
createNewKeyIfNeeded(false);
|
||||||
|
keyStore.load(null);
|
||||||
|
final SecretKey key = (SecretKey) keyStore.getKey(ALIAS_KEY, null);
|
||||||
|
|
||||||
|
// important to restore spec here that was used for decryption
|
||||||
|
final byte[] iv = Base64.decode(ivSpecValue, Base64.DEFAULT);
|
||||||
|
final IvParameterSpec spec = new IvParameterSpec(iv);
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, key, spec);
|
||||||
|
|
||||||
|
stopListening();
|
||||||
|
startListening();
|
||||||
|
|
||||||
|
} catch (final KeyPermanentlyInvalidatedException invalidKeyException) {
|
||||||
|
fingerPrintCallback.onInvalidKeyException();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
fingerPrintCallback.onException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("NewApi")
|
||||||
|
public void decryptData(final String encryptedValue) {
|
||||||
|
|
||||||
|
if (!isFingerprintInitialized()) {
|
||||||
|
if (fingerPrintCallback != null) {
|
||||||
|
fingerPrintCallback.onException();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// actual decryption here
|
||||||
|
final byte[] encrypted = Base64.decode(encryptedValue, 0);
|
||||||
|
byte[] decrypted = cipher.doFinal(encrypted);
|
||||||
|
final String decryptedString = new String(decrypted);
|
||||||
|
|
||||||
|
//final String encryptedString = Base64.encodeToString(encrypted, 0 /* flags */);
|
||||||
|
fingerPrintCallback.handleDecryptedResult(decryptedString);
|
||||||
|
|
||||||
|
} catch (final Exception e) {
|
||||||
|
fingerPrintCallback.onException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
private void createNewKeyIfNeeded(final boolean allowDeleteExisting) {
|
||||||
|
if (!isFingerprintInitialized()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
keyStore.load(null);
|
||||||
|
if (allowDeleteExisting
|
||||||
|
&& keyStore.containsAlias(ALIAS_KEY)) {
|
||||||
|
|
||||||
|
keyStore.deleteEntry(ALIAS_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new key if needed
|
||||||
|
if (!keyStore.containsAlias(ALIAS_KEY)) {
|
||||||
|
// Set the alias of the entry in Android KeyStore where the key will appear
|
||||||
|
// and the constrains (purposes) in the constructor of the Builder
|
||||||
|
keyGenerator.init(
|
||||||
|
new KeyGenParameterSpec.Builder(
|
||||||
|
ALIAS_KEY,
|
||||||
|
KeyProperties.PURPOSE_ENCRYPT |
|
||||||
|
KeyProperties.PURPOSE_DECRYPT)
|
||||||
|
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
|
||||||
|
// Require the user to authenticate with a fingerprint to authorize every use
|
||||||
|
// of the key
|
||||||
|
.setUserAuthenticationRequired(true)
|
||||||
|
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
|
||||||
|
.build());
|
||||||
|
keyGenerator.generateKey();
|
||||||
|
}
|
||||||
|
} catch (final Exception e) {
|
||||||
|
fingerPrintCallback.onException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
public boolean isHardwareDetected() {
|
||||||
|
return isFingerprintSupported()
|
||||||
|
&& fingerprintManager != null
|
||||||
|
&& fingerprintManager.isHardwareDetected();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
public boolean hasEnrolledFingerprints() {
|
||||||
|
// fingerprint hardware supported and api level OK
|
||||||
|
return isHardwareDetected()
|
||||||
|
// fingerprints enrolled
|
||||||
|
&& fingerprintManager != null
|
||||||
|
&& fingerprintManager.hasEnrolledFingerprints()
|
||||||
|
// and lockscreen configured
|
||||||
|
&& keyguardManager.isKeyguardSecure();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setInitOk(final boolean initOk) {
|
||||||
|
this.initOk = initOk;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFingerprintSupported() {
|
||||||
|
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
BIN
app/src/main/res/drawable-hdpi/ic_fp_40px.png
Normal file
BIN
app/src/main/res/drawable-hdpi/ic_fp_40px.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.8 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_fp_40px.png
Normal file
BIN
app/src/main/res/drawable-xhdpi/ic_fp_40px.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
BIN
app/src/main/res/drawable-xxhdpi/ic_fp_40px.png
Normal file
BIN
app/src/main/res/drawable-xxhdpi/ic_fp_40px.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
@@ -19,7 +19,8 @@
|
|||||||
-->
|
-->
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="fill_parent">
|
android:layout_height="fill_parent"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
<TextView android:id="@+id/filename_label"
|
<TextView android:id="@+id/filename_label"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -57,20 +58,45 @@
|
|||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/password_label"
|
android:layout_below="@id/password_label"
|
||||||
|
android:layout_toLeftOf="@+id/fingerprint"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:inputType="textPassword"
|
android:inputType="textPassword"
|
||||||
android:hint="@string/hint_login_pass"/>
|
android:hint="@string/hint_login_pass"/>
|
||||||
|
|
||||||
|
<!-- added these 2 fingerprint related views -->
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/fingerprint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="@dimen/margin_small"
|
||||||
|
android:layout_marginRight="@dimen/margin_small"
|
||||||
|
android:layout_below="@id/password_label"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:src="@drawable/ic_fp_40px"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"
|
||||||
|
/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/fingerprint_label"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/fingerprint"
|
||||||
|
android:text="@string/entry_and_or"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"
|
||||||
|
/>
|
||||||
|
|
||||||
<ImageButton android:id="@+id/browse_button"
|
<ImageButton android:id="@+id/browse_button"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:src="@drawable/ic_launcher_folder_small"
|
android:src="@drawable/ic_launcher_folder_small"
|
||||||
android:layout_below="@id/password"
|
android:layout_below="@id/fingerprint_label"
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
/>
|
/>
|
||||||
<EditText android:id="@+id/pass_keyfile"
|
<EditText android:id="@+id/pass_keyfile"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/password"
|
android:layout_below="@id/fingerprint_label"
|
||||||
android:layout_toLeftOf="@id/browse_button"
|
android:layout_toLeftOf="@id/browse_button"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:hint="@string/entry_keyfile"/>
|
android:hint="@string/entry_keyfile"/>
|
||||||
|
|||||||
8
app/src/main/res/values/dimens.xml
Normal file
8
app/src/main/res/values/dimens.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<dimen name="margin_tiny">4dp</dimen>
|
||||||
|
<dimen name="margin_small">8dp</dimen>
|
||||||
|
<dimen name="margin_medium">16dp</dimen>
|
||||||
|
<dimen name="margin_large">32dp</dimen>
|
||||||
|
<dimen name="margin_huge">64dp</dimen>
|
||||||
|
</resources>
|
||||||
@@ -194,6 +194,13 @@
|
|||||||
<string name="warning_read_only">Your sd card is currently read-only. You may not be able to save changes to your database.</string>
|
<string name="warning_read_only">Your sd card is currently read-only. You may not be able to save changes to your database.</string>
|
||||||
<string name="warning_unmounted">Your sd card is not currently mounted on your device. You will not be able to load or create your database.</string>
|
<string name="warning_unmounted">Your sd card is not currently mounted on your device. You will not be able to load or create your database.</string>
|
||||||
<string name="version_label">Version:</string>
|
<string name="version_label">Version:</string>
|
||||||
|
<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_error">Fingerprint problem</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-array name="clipboard_timeout_options">
|
<string-array name="clipboard_timeout_options">
|
||||||
<item>30 seconds</item>
|
<item>30 seconds</item>
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
jcenter()
|
jcenter()
|
||||||
|
maven {
|
||||||
|
url 'https://maven.google.com/'
|
||||||
|
name 'Google'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.0.0'
|
classpath 'com.android.tools.build:gradle:3.0.0'
|
||||||
@@ -11,5 +15,9 @@ buildscript {
|
|||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
jcenter()
|
jcenter()
|
||||||
|
maven {
|
||||||
|
url 'https://maven.google.com/'
|
||||||
|
name 'Google'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user