Ask for the backup password if it is not available

Fixes #182
This commit is contained in:
Jakob Nixdorf 2018-07-04 13:39:40 +02:00
parent 8effb9adf6
commit 18e9c4b69f
No known key found for this signature in database
GPG key ID: BE99BF86574A7DBC
7 changed files with 239 additions and 57 deletions

View file

@ -49,6 +49,7 @@ import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.util.OpenPgpApi;
import org.openintents.openpgp.util.OpenPgpServiceConnection;
import org.shadowice.flocke.andotp.Database.Entry;
import org.shadowice.flocke.andotp.Dialogs.PasswordEntryDialog;
import org.shadowice.flocke.andotp.R;
import org.shadowice.flocke.andotp.Utilities.Constants;
import org.shadowice.flocke.andotp.Utilities.DatabaseHelper;
@ -122,12 +123,8 @@ public class BackupActivity extends BaseActivity {
if (settings.getBackupPasswordEnc().isEmpty()) {
cryptSetup.setVisibility(View.VISIBLE);
backupCrypt.setVisibility(View.GONE);
restoreCrypt.setVisibility(View.GONE);
} else {
cryptSetup.setVisibility(View.GONE);
backupCrypt.setVisibility(View.VISIBLE);
restoreCrypt.setVisibility(View.VISIBLE);
}
backupCrypt.setOnClickListener(new View.OnClickListener() {
@ -418,69 +415,89 @@ public class BackupActivity extends BaseActivity {
/* Encrypted backup functions */
private void doRestoreCrypt(Uri uri) {
private void doRestoreCrypt(final Uri uri) {
String password = settings.getBackupPasswordEnc();
if (! password.isEmpty()) {
if (Tools.isExternalStorageReadable()) {
boolean success = true;
String decryptedString = "";
try {
byte[] encrypted = FileHelper.readFileToBytes(this, uri);
SecretKey key = EncryptionHelper.generateSymmetricKeyFromPassword(password);
byte[] decrypted = EncryptionHelper.decrypt(key, encrypted);
decryptedString = new String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) {
success = false;
e.printStackTrace();
if (password.isEmpty()) {
PasswordEntryDialog pwDialog = new PasswordEntryDialog(this, PasswordEntryDialog.Mode.ENTER, new PasswordEntryDialog.PasswordEnteredCallback() {
@Override
public void onPasswordEntered(String newPassword) {
doRestoreCryptWithPassword(uri, newPassword);
}
if (success) {
restoreEntries(decryptedString);
} else {
Toast.makeText(this,R.string.backup_toast_import_decryption_failed, Toast.LENGTH_LONG).show();
}
} else {
Toast.makeText(this, R.string.backup_toast_storage_not_accessible, Toast.LENGTH_LONG).show();
}
});
pwDialog.show();
} else {
Toast.makeText(this, R.string.backup_toast_crypt_password_not_set, Toast.LENGTH_LONG).show();
doRestoreCryptWithPassword(uri, password);
}
}
private void doBackupCrypt(Uri uri) {
String password = settings.getBackupPasswordEnc();
private void doRestoreCryptWithPassword(Uri uri, String password) {
if (Tools.isExternalStorageReadable()) {
boolean success = true;
String decryptedString = "";
if (! password.isEmpty()) {
if (Tools.isExternalStorageWritable()) {
ArrayList<Entry> entries = DatabaseHelper.loadDatabase(this, encryptionKey);
String plain = DatabaseHelper.entriesToString(entries);
try {
byte[] encrypted = FileHelper.readFileToBytes(this, uri);
boolean success = true;
SecretKey key = EncryptionHelper.generateSymmetricKeyFromPassword(password);
byte[] decrypted = EncryptionHelper.decrypt(key, encrypted);
try {
SecretKey key = EncryptionHelper.generateSymmetricKeyFromPassword(password);
byte[] encrypted = EncryptionHelper.encrypt(key, plain.getBytes(StandardCharsets.UTF_8));
decryptedString = new String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) {
success = false;
e.printStackTrace();
}
FileHelper.writeBytesToFile(this, uri, encrypted);
} catch (Exception e) {
e.printStackTrace();
success = false;
}
if (success) {
Toast.makeText(this, R.string.backup_toast_export_success, Toast.LENGTH_LONG).show();
} else {
Toast.makeText(this, R.string.backup_toast_export_failed, Toast.LENGTH_LONG).show();
}
if (success) {
restoreEntries(decryptedString);
} else {
Toast.makeText(this, R.string.backup_toast_storage_not_accessible, Toast.LENGTH_LONG).show();
Toast.makeText(this,R.string.backup_toast_import_decryption_failed, Toast.LENGTH_LONG).show();
}
} else {
Toast.makeText(this, R.string.backup_toast_crypt_password_not_set, Toast.LENGTH_LONG).show();
Toast.makeText(this, R.string.backup_toast_storage_not_accessible, Toast.LENGTH_LONG).show();
}
}
private void doBackupCrypt(final Uri uri) {
String password = settings.getBackupPasswordEnc();
if (password.isEmpty()) {
PasswordEntryDialog pwDialog = new PasswordEntryDialog(this, PasswordEntryDialog.Mode.UPDATE, new PasswordEntryDialog.PasswordEnteredCallback() {
@Override
public void onPasswordEntered(String newPassword) {
doBackupCryptWithPassword(uri, newPassword);
}
});
pwDialog.show();
} else {
doBackupCryptWithPassword(uri, password);
}
}
private void doBackupCryptWithPassword(Uri uri, String password) {
if (Tools.isExternalStorageWritable()) {
ArrayList<Entry> entries = DatabaseHelper.loadDatabase(this, encryptionKey);
String plain = DatabaseHelper.entriesToString(entries);
boolean success = true;
try {
SecretKey key = EncryptionHelper.generateSymmetricKeyFromPassword(password);
byte[] encrypted = EncryptionHelper.encrypt(key, plain.getBytes(StandardCharsets.UTF_8));
FileHelper.writeBytesToFile(this, uri, encrypted);
} catch (Exception e) {
e.printStackTrace();
success = false;
}
if (success) {
Toast.makeText(this, R.string.backup_toast_export_success, Toast.LENGTH_LONG).show();
} else {
Toast.makeText(this, R.string.backup_toast_export_failed, Toast.LENGTH_LONG).show();
}
} else {
Toast.makeText(this, R.string.backup_toast_storage_not_accessible, Toast.LENGTH_LONG).show();
}
finishWithResult();

View file

@ -0,0 +1,80 @@
package org.shadowice.flocke.andotp.Dialogs;
import android.content.Context;
import android.support.design.widget.TextInputEditText;
import android.support.v7.app.AppCompatDialog;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import org.shadowice.flocke.andotp.R;
import org.shadowice.flocke.andotp.Utilities.Tools;
public class PasswordEntryDialog extends AppCompatDialog
implements View.OnClickListener, TextWatcher {
public enum Mode { ENTER, UPDATE }
public interface PasswordEnteredCallback {
void onPasswordEntered(String newPassword);
}
private Mode dialogMode;
private PasswordEnteredCallback callback;
private TextInputEditText passwordInput;
private EditText passwordConfirm;
private Button okButton;
public PasswordEntryDialog(Context context, Mode newMode, PasswordEnteredCallback newCallback) {
super(context, Tools.getThemeResource(context, R.attr.dialogTheme));
setTitle(R.string.dialog_title_enter_password);
setContentView(R.layout.dialog_password_entry);
passwordInput = findViewById(R.id.passwordInput);
passwordConfirm = findViewById(R.id.passwordConfirm);
okButton = findViewById(R.id.buttonOk);
Button cancelButton = findViewById(R.id.buttonCancel);
okButton.setOnClickListener(this);
cancelButton.setOnClickListener(this);
this.callback = newCallback;
this.dialogMode = newMode;
if (this.dialogMode == Mode.UPDATE) {
passwordConfirm.setVisibility(View.VISIBLE);
passwordInput.addTextChangedListener(this);
passwordConfirm.addTextChangedListener(this);
} else if (this.dialogMode == Mode.ENTER) {
passwordConfirm.setVisibility(View.GONE);
}
}
// TextWatcher
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (TextUtils.equals(passwordInput.getEditableText(), passwordConfirm.getEditableText()))
okButton.setEnabled(true);
else
okButton.setEnabled(false);
}
public void afterTextChanged(Editable s) {}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
// View.OnClickListener
public void onClick(View view) {
if (view.getId() == R.id.buttonOk) {
if (callback != null)
callback.onPasswordEntered(passwordInput.getText().toString());
}
dismiss();
}
}

View file

@ -25,14 +25,12 @@ package org.shadowice.flocke.andotp.Utilities;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.support.annotation.ColorInt;
import java.io.File;
import java.text.DateFormat;
@ -67,6 +65,16 @@ public class Tools {
return colorValue;
}
public static int getThemeResource(Context context, int styleAttr) {
Resources.Theme theme = context.getTheme();
TypedArray arr = theme.obtainStyledAttributes(new int[]{styleAttr});
int styleValue = arr.getResourceId(0, -1);
arr.recycle();
return styleValue;
}
/* Create a ColorFilter based on the current theme */
public static ColorFilter getThemeColorFilter(Context context, int colorAttr) {
return new PorterDuffColorFilter(getThemeColor(context, colorAttr), PorterDuff.Mode.SRC_IN);

View file

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/activity_margin"
android:layout_marginStart="@dimen/activity_margin_small"
android:layout_marginEnd="@dimen/activity_margin_small"
android:orientation="vertical"
android:background="@null"
android:padding="@dimen/activity_margin">
<android.support.design.widget.TextInputLayout
android:id="@+id/passwordInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/dialog_label_enter_password"
app:passwordToggleEnabled="true" >
<android.support.design.widget.TextInputEditText
android:id="@+id/passwordInput"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:inputType="textPassword" />
</android.support.design.widget.TextInputLayout>
<EditText
android:id="@+id/passwordConfirm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/dialog_label_confirm_password"
android:inputType="textPassword" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/activity_margin"
android:gravity="end">
<Button
android:id="@+id/buttonCancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Widget.AppCompat.Button.ButtonBar.AlertDialog"
android:text="@android:string/cancel"/>
<Button
android:id="@+id/buttonOk"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Widget.AppCompat.Button.ButtonBar.AlertDialog"
android:text="@android:string/ok" />
</LinearLayout>
</LinearLayout>

View file

@ -22,8 +22,9 @@
<string name="backup_desc_import_crypt">Restore accounts from a password-protected JSON file</string>
<string name="backup_desc_import_openpgp">Restore accounts from an OpenPGP-encrypted JSON file</string>
<string name="backup_desc_crypt_setup">You need to set a <b>Backup password</b> in the
<b>Settings</b> to use encrypted backups.
<string name="backup_desc_crypt_setup">Failed to load the backup password from the <b>Settings</b>,
this either means no password was set or something went wrong. You will be asked to enter
the password manually when creating or importing a backup.
</string>
<string name="backup_desc_openpgp_provider">You need to install an OpenPGP provider and enable

View file

@ -69,6 +69,11 @@
<string name="dialog_title_last_used">Last used</string>
<string name="dialog_title_keystore_error">KeyStore error</string>
<string name="dialog_title_enter_password">Enter password</string>
<string name="dialog_label_enter_password">Enter password</string>
<string name="dialog_label_confirm_password">Confirm password</string>
<string name="dialog_msg_auth">Please enter your device credentials to start andOTP.</string>
<string name="dialog_msg_confirm_delete">Are you sure you want do remove the account \"%1$s\"?</string>

View file

@ -2,6 +2,7 @@
<!-- Definitions -->
<attr name="windowBackground" format="reference" />
<attr name="cardStyle" format="reference" />
<attr name="dialogTheme" format="reference" />
<attr name="colorGithub" format="reference" />
<attr name="colorPaypal" format="reference" />
@ -27,6 +28,7 @@
<item name="thumbnailBackground">@android:color/transparent</item>
<item name="cardStyle">@style/CardViewStyle</item>
<item name="dialogTheme">@style/DialogTheme</item>
<item name="about_libraries_card">@color/cardview_light_background</item>
<item name="about_libraries_title_openSource">@color/about_libraries_text_openSource</item>
@ -44,6 +46,10 @@
<item name="contentPadding">@dimen/activity_margin</item>
</style>
<style name="DialogTheme" parent="Theme.AppCompat.Light.Dialog.MinWidth">
<item name="colorAccent">@color/colorAccent</item>
</style>
<!-- Dark application theme. -->
<style name="AppTheme.Dark" parent="Theme.AppCompat">
<item name="colorPrimary">@color/colorPrimary</item>
@ -57,6 +63,7 @@
<item name="colorPaypal">@color/paypal_light</item>
<item name="cardStyle">@style/CardViewStyle</item>
<item name="dialogTheme">@style/DialogTheme.Dark</item>
<item name="about_libraries_card">@color/cardview_dark_background</item>
<item name="about_libraries_title_openSource">@color/about_libraries_text_openSource_dark</item>
@ -70,6 +77,10 @@
<item name="windowNoTitle">true</item>
</style>
<style name="DialogTheme.Dark" parent="Theme.AppCompat.Dialog.MinWidth">
<item name="colorAccent">@color/colorAccent</item>
</style>
<!-- Black application theme. -->
<style name="AppTheme.Black" parent="Theme.AppCompat">
<item name="colorPrimary">@color/colorPrimary</item>
@ -83,6 +94,7 @@
<item name="colorPaypal">@color/paypal_light</item>
<item name="cardStyle">@style/CardViewStyle.Black</item>
<item name="dialogTheme">@style/DialogTheme.Dark</item>
<item name="about_libraries_card">@android:color/black</item>
<item name="about_libraries_title_openSource">@color/about_libraries_text_openSource_dark</item>