parent
8effb9adf6
commit
18e9c4b69f
7 changed files with 239 additions and 57 deletions
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
59
app/src/main/res/layout/dialog_password_entry.xml
Normal file
59
app/src/main/res/layout/dialog_password_entry.xml
Normal 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>
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue