Try the encryption change in the SettingsActivity and restore backup if it fails

This commit is contained in:
Jakob Nixdorf 2018-01-03 13:23:13 +01:00
parent 64fbdd93d3
commit a20dc326bc
No known key found for this signature in database
GPG key ID: BE99BF86574A7DBC
7 changed files with 193 additions and 80 deletions

View file

@ -53,16 +53,13 @@ import static org.shadowice.flocke.andotp.Utilities.Constants.AuthMethod;
public class AuthenticateActivity extends ThemedActivity
implements EditText.OnEditorActionListener {
public static final String AUTH_EXTRA_NAME_PASSWORD_KEY = "password_key";
public static final String AUTH_EXTRA_NAME_FATAL = "fatal";
public static final String AUTH_EXTRA_NAME_SAVE_DATABASE = "save_database";
public static final String AUTH_EXTRA_NAME_NEW_ENCRYPTION = "new_encryption";
public static final String AUTH_EXTRA_NAME_MESSAGE = "message";
boolean saveDatabase = false;
boolean fatal = true;
private String password;
AuthMethod authMethod;
String newEncryption = "";
boolean oldPassword = false;
TextInputEditText passwordInput;
@ -84,8 +81,7 @@ public class AuthenticateActivity extends ThemedActivity
Intent callingIntent = getIntent();
int labelMsg = callingIntent.getIntExtra(AUTH_EXTRA_NAME_MESSAGE, R.string.auth_msg_authenticate);
saveDatabase = callingIntent.getBooleanExtra(AUTH_EXTRA_NAME_SAVE_DATABASE, false);
fatal = callingIntent.getBooleanExtra(AUTH_EXTRA_NAME_FATAL, true);
newEncryption = callingIntent.getStringExtra(AUTH_EXTRA_NAME_NEW_ENCRYPTION);
TextView passwordLabel = v.findViewById(R.id.passwordLabel);
TextInputLayout passwordLayout = v.findViewById(R.id.passwordLayout);
@ -173,10 +169,10 @@ public class AuthenticateActivity extends ThemedActivity
// End with a result
public void finishWithResult(boolean success, byte[] key) {
if (success || fatal) {
Intent data = new Intent();
data.putExtra(AUTH_EXTRA_NAME_SAVE_DATABASE, saveDatabase);
if (newEncryption != null && ! newEncryption.isEmpty())
data.putExtra(AUTH_EXTRA_NAME_NEW_ENCRYPTION, newEncryption);
if (key != null)
data.putExtra(AUTH_EXTRA_NAME_PASSWORD_KEY, key);
@ -185,14 +181,6 @@ public class AuthenticateActivity extends ThemedActivity
setResult(RESULT_OK, data);
finish();
} else {
passwordInput.setText("");
if (authMethod == AuthMethod.PASSWORD)
Toast.makeText(this, R.string.auth_toast_password_again, Toast.LENGTH_LONG).show();
else if (authMethod == AuthMethod.PIN)
Toast.makeText(this, R.string.auth_toast_pin_again, Toast.LENGTH_LONG).show();
}
}
// Go back to the main activity

View file

@ -62,7 +62,6 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
public class BackupActivity extends BaseActivity {
private final static int INTENT_OPEN_DOCUMENT_PLAIN = 200;
@ -88,7 +87,7 @@ public class BackupActivity extends BaseActivity {
private static final String DEFAULT_BACKUP_MIMETYPE_CRYPT = "binary/aes";
private static final String DEFAULT_BACKUP_MIMETYPE_PGP = "application/pgp-encrypted";
public static final String EXTRA_NAME_ENCRYPTION_KEY = "encryption_key";
public static final String BACKUP_EXTRA_NAME_ENCRYPTION_KEY = "encryption_key";
private SecretKey encryptionKey = null;
@ -117,8 +116,8 @@ public class BackupActivity extends BaseActivity {
View v = stub.inflate();
Intent callingIntent = getIntent();
byte[] keyMaterial = callingIntent.getByteArrayExtra(EXTRA_NAME_ENCRYPTION_KEY);
encryptionKey = new SecretKeySpec(keyMaterial, 0, keyMaterial.length, "AES");
byte[] keyMaterial = callingIntent.getByteArrayExtra(BACKUP_EXTRA_NAME_ENCRYPTION_KEY);
encryptionKey = EncryptionHelper.generateSymmetricKey(keyMaterial);
// Plain-text

View file

@ -72,11 +72,9 @@ import java.util.HashMap;
import javax.crypto.SecretKey;
import static org.shadowice.flocke.andotp.Activities.AuthenticateActivity.AUTH_EXTRA_NAME_FATAL;
import static org.shadowice.flocke.andotp.Activities.AuthenticateActivity.AUTH_EXTRA_NAME_MESSAGE;
import static org.shadowice.flocke.andotp.Activities.AuthenticateActivity.AUTH_EXTRA_NAME_PASSWORD_KEY;
import static org.shadowice.flocke.andotp.Activities.AuthenticateActivity.AUTH_EXTRA_NAME_SAVE_DATABASE;
import static org.shadowice.flocke.andotp.Activities.BackupActivity.EXTRA_NAME_ENCRYPTION_KEY;
import static org.shadowice.flocke.andotp.Activities.BackupActivity.BACKUP_EXTRA_NAME_ENCRYPTION_KEY;
import static org.shadowice.flocke.andotp.Activities.SettingsActivity.SETTINGS_EXTRA_NAME_ENCRYPTION_CHANGED;
import static org.shadowice.flocke.andotp.Activities.SettingsActivity.SETTINGS_EXTRA_NAME_ENCRYPTION_KEY;
import static org.shadowice.flocke.andotp.Utilities.Constants.AuthMethod;
@ -130,7 +128,7 @@ public class MainActivity extends BaseActivity
.show();
}
public void authenticate(int messageId, boolean saveDatabase, boolean fatal) {
public void authenticate(int messageId) {
AuthMethod authMethod = settings.getAuthMethod();
if (authMethod == AuthMethod.DEVICE) {
@ -141,8 +139,6 @@ public class MainActivity extends BaseActivity
}
} else if (authMethod == AuthMethod.PASSWORD || authMethod == AuthMethod.PIN) {
Intent authIntent = new Intent(this, AuthenticateActivity.class);
authIntent.putExtra(AUTH_EXTRA_NAME_SAVE_DATABASE, saveDatabase);
authIntent.putExtra(AUTH_EXTRA_NAME_FATAL, fatal);
authIntent.putExtra(AUTH_EXTRA_NAME_MESSAGE, messageId);
startActivityForResult(authIntent, INTENT_INTERNAL_AUTHENTICATE);
}
@ -324,7 +320,7 @@ public class MainActivity extends BaseActivity
if (requireAuthentication) {
if (settings.getAuthMethod() != AuthMethod.NONE) {
requireAuthentication = false;
authenticate(R.string.auth_msg_authenticate,false, true);
authenticate(R.string.auth_msg_authenticate);
}
} else {
if (encryptionType == EncryptionType.KEYSTORE) {
@ -335,7 +331,7 @@ public class MainActivity extends BaseActivity
populateAdapter();
} else if (encryptionType == EncryptionType.PASSWORD) {
if (adapter.getEncryptionKey() == null) {
authenticate(R.string.auth_msg_authenticate,false, true);
authenticate(R.string.auth_msg_authenticate);
} else {
populateAdapter();
}
@ -396,11 +392,10 @@ public class MainActivity extends BaseActivity
}
} else if (requestCode == INTENT_INTERNAL_SETTINGS && resultCode == RESULT_OK) {
boolean encryptionChanged = intent.getBooleanExtra(SETTINGS_EXTRA_NAME_ENCRYPTION_CHANGED, false);
if (encryptionChanged) {
byte[] newKey = intent.getByteArrayExtra(SETTINGS_EXTRA_NAME_ENCRYPTION_KEY);
updateEncryption(newKey, true);
}
if (encryptionChanged)
updateEncryption(newKey);
} else if (requestCode == INTENT_INTERNAL_AUTHENTICATE) {
if (resultCode != RESULT_OK) {
Toast.makeText(getBaseContext(), R.string.toast_auth_failed_fatal, Toast.LENGTH_LONG).show();
@ -413,15 +408,13 @@ public class MainActivity extends BaseActivity
} else {
requireAuthentication = false;
boolean saveDatabase = intent.getBooleanExtra(AUTH_EXTRA_NAME_SAVE_DATABASE, false);
byte[] authKey = intent.getByteArrayExtra(AUTH_EXTRA_NAME_PASSWORD_KEY);
updateEncryption(authKey, saveDatabase);
updateEncryption(authKey);
}
}
}
private void updateEncryption(byte[] newKey, boolean saveDatabase) {
private void updateEncryption(byte[] newKey) {
SecretKey encryptionKey = null;
encryptionType = settings.getEncryption();
@ -432,17 +425,13 @@ public class MainActivity extends BaseActivity
if (newKey != null && newKey.length > 0) {
encryptionKey = EncryptionHelper.generateSymmetricKey(newKey);
} else {
authenticate(R.string.auth_msg_confirm_encryption, true, false);
authenticate(R.string.auth_msg_confirm_encryption);
}
}
if (encryptionKey != null) {
if (encryptionKey != null)
adapter.setEncryptionKey(encryptionKey);
if (saveDatabase)
adapter.saveEntries();
}
populateAdapter();
}
@ -520,10 +509,11 @@ public class MainActivity extends BaseActivity
if (id == R.id.action_backup) {
Intent backupIntent = new Intent(this, BackupActivity.class);
backupIntent.putExtra(EXTRA_NAME_ENCRYPTION_KEY, adapter.getEncryptionKey().getEncoded());
backupIntent.putExtra(BACKUP_EXTRA_NAME_ENCRYPTION_KEY, adapter.getEncryptionKey().getEncoded());
startActivityForResult(backupIntent, INTENT_INTERNAL_BACKUP);
} else if (id == R.id.action_settings) {
Intent settingsIntent = new Intent(this, SettingsActivity.class);
settingsIntent.putExtra(SETTINGS_EXTRA_NAME_ENCRYPTION_KEY, adapter.getEncryptionKey().getEncoded());
startActivityForResult(settingsIntent, INTENT_INTERNAL_SETTINGS);
} else if (id == R.id.action_about){
Intent aboutIntent = new Intent(this, AboutActivity.class);

View file

@ -36,21 +36,36 @@ import android.widget.Toast;
import org.openintents.openpgp.util.OpenPgpAppPreference;
import org.openintents.openpgp.util.OpenPgpKeyPreference;
import org.shadowice.flocke.andotp.Database.Entry;
import org.shadowice.flocke.andotp.Preferences.CredentialsPreference;
import org.shadowice.flocke.andotp.R;
import org.shadowice.flocke.andotp.Utilities.DatabaseHelper;
import org.shadowice.flocke.andotp.Utilities.EncryptionHelper;
import org.shadowice.flocke.andotp.Utilities.KeyStoreHelper;
import org.shadowice.flocke.andotp.Utilities.Settings;
import java.util.ArrayList;
import javax.crypto.SecretKey;
import static org.shadowice.flocke.andotp.Activities.AuthenticateActivity.AUTH_EXTRA_NAME_MESSAGE;
import static org.shadowice.flocke.andotp.Activities.AuthenticateActivity.AUTH_EXTRA_NAME_NEW_ENCRYPTION;
import static org.shadowice.flocke.andotp.Activities.AuthenticateActivity.AUTH_EXTRA_NAME_PASSWORD_KEY;
import static org.shadowice.flocke.andotp.Utilities.Constants.AuthMethod;
import static org.shadowice.flocke.andotp.Utilities.Constants.EncryptionType;
public class SettingsActivity extends BaseActivity
implements SharedPreferences.OnSharedPreferenceChangeListener{
private static final int INTENT_INTERNAL_AUTHENTICATE = 300;
public static final String SETTINGS_EXTRA_NAME_ENCRYPTION_CHANGED = "encryption_changed";
public static final String SETTINGS_EXTRA_NAME_ENCRYPTION_KEY = "encryption_key";
SettingsFragment fragment;
SecretKey encryptionKey = null;
boolean encryptionChanged = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -64,6 +79,10 @@ public class SettingsActivity extends BaseActivity
ViewStub stub = findViewById(R.id.container_stub);
stub.inflate();
Intent callingIntent = getIntent();
byte[] keyMaterial = callingIntent.getByteArrayExtra(SETTINGS_EXTRA_NAME_ENCRYPTION_KEY);
encryptionKey = EncryptionHelper.generateSymmetricKey(keyMaterial);
fragment = new SettingsFragment();
getFragmentManager().beginTransaction()
@ -74,13 +93,11 @@ public class SettingsActivity extends BaseActivity
sharedPref.registerOnSharedPreferenceChangeListener(this);
}
public void finishWithResult(boolean encryptionChanged, byte[] newKey) {
public void finishWithResult() {
Intent data = new Intent();
data.putExtra(SETTINGS_EXTRA_NAME_ENCRYPTION_CHANGED, encryptionChanged);
if (newKey != null)
data.putExtra(SETTINGS_EXTRA_NAME_ENCRYPTION_KEY, newKey);
data.putExtra(SETTINGS_EXTRA_NAME_ENCRYPTION_KEY, encryptionKey.getEncoded());
setResult(RESULT_OK, data);
finish();
@ -88,13 +105,13 @@ public class SettingsActivity extends BaseActivity
@Override
public boolean onSupportNavigateUp() {
finishWithResult(false,null);
finishWithResult();
return true;
}
@Override
public void onBackPressed() {
finishWithResult(false, null);
finishWithResult();
super.onBackPressed();
}
@ -106,10 +123,75 @@ public class SettingsActivity extends BaseActivity
}
}
private void tryEncryptionChangeWithAuth(EncryptionType newEnc) {
Intent authIntent = new Intent(this, AuthenticateActivity.class);
authIntent.putExtra(AUTH_EXTRA_NAME_NEW_ENCRYPTION, newEnc.name());
authIntent.putExtra(AUTH_EXTRA_NAME_MESSAGE, R.string.auth_msg_confirm_encryption);
startActivityForResult(authIntent, INTENT_INTERNAL_AUTHENTICATE);
}
private boolean tryEncryptionChange(EncryptionType newEnc, byte[] newKey) {
Toast upgrading = Toast.makeText(this, R.string.settings_toast_encryption_changing, Toast.LENGTH_LONG);
upgrading.show();
if (DatabaseHelper.backupDatabase(this)) {
ArrayList<Entry> entries = DatabaseHelper.loadDatabase(this, encryptionKey);
SecretKey newEncryptionKey;
if (newEnc == EncryptionType.KEYSTORE) {
newEncryptionKey = KeyStoreHelper.loadEncryptionKeyFromKeyStore(this);
} else if (newKey != null && newKey.length > 0) {
newEncryptionKey = EncryptionHelper.generateSymmetricKey(newKey);
} else {
upgrading.cancel();
DatabaseHelper.restoreDatabaseBackup(this);
return false;
}
if (DatabaseHelper.saveDatabase(this, entries, newEncryptionKey)) {
encryptionKey = newEncryptionKey;
encryptionChanged = true;
fragment.encryption.setValue(newEnc.name().toLowerCase());
upgrading.cancel();
Toast.makeText(this, R.string.settings_toast_encryption_change_success, Toast.LENGTH_LONG).show();
return true;
}
DatabaseHelper.restoreDatabaseBackup(this);
upgrading.cancel();
Toast.makeText(this, R.string.settings_toast_encryption_change_failed, Toast.LENGTH_LONG).show();
} else {
upgrading.cancel();
Toast.makeText(this, R.string.settings_toast_encryption_backup_failed, Toast.LENGTH_LONG).show();
}
return false;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (fragment.pgpKey.handleOnActivityResult(requestCode, resultCode, data)) {
if (requestCode == INTENT_INTERNAL_AUTHENTICATE) {
if (resultCode == RESULT_OK) {
byte[] authKey = data.getByteArrayExtra(AUTH_EXTRA_NAME_PASSWORD_KEY);
String newEnc = data.getStringExtra(AUTH_EXTRA_NAME_NEW_ENCRYPTION);
if (authKey != null && authKey.length > 0 && newEnc != null && !newEnc.isEmpty()) {
EncryptionType newEncType = EncryptionType.valueOf(newEnc);
tryEncryptionChange(newEncType, authKey);
} else {
Toast.makeText(this, R.string.settings_toast_encryption_no_key, Toast.LENGTH_LONG).show();
}
} else {
Toast.makeText(this, R.string.settings_toast_encryption_auth_failed, Toast.LENGTH_LONG).show();
}
} else if (fragment.pgpKey.handleOnActivityResult(requestCode, resultCode, data)) {
// handled by OpenPgpKeyPreference
return;
}
@ -134,10 +216,10 @@ public class SettingsActivity extends BaseActivity
addPreferencesFromResource(R.xml.preferences);
CredentialsPreference credentialsPreference = (CredentialsPreference) findPreference(getString(R.string.settings_key_auth));
credentialsPreference.setEncryptionChangeHandler(new CredentialsPreference.EncryptionChangeHandler() {
credentialsPreference.setEncryptionChangeCallback(new CredentialsPreference.EncryptionChangeCallback() {
@Override
public void onEncryptionChanged(byte[] newKey) {
((SettingsActivity) getActivity()).finishWithResult(true, newKey);
public boolean testEncryptionChange(byte[] newKey) {
return ((SettingsActivity) getActivity()).tryEncryptionChange(settings.getEncryption(), newKey);
}
});
@ -160,16 +242,15 @@ public class SettingsActivity extends BaseActivity
if (settings.getAuthCredentials(authMethod).isEmpty()) {
Toast.makeText(getActivity(), R.string.settings_toast_encryption_invalid_without_credentials, Toast.LENGTH_LONG).show();
return false;
} else {
KeyStoreHelper.wipeKeys(preference.getContext());
}
}
}
encryption.setValue(newEncryption);
((SettingsActivity) getActivity()).finishWithResult(true, null);
((SettingsActivity) getActivity()).tryEncryptionChangeWithAuth(encryptionType);
} else if (encryptionType == EncryptionType.KEYSTORE) {
((SettingsActivity) getActivity()).tryEncryptionChange(encryptionType, null);
}
return true;
return false;
}
});

View file

@ -25,7 +25,6 @@ package org.shadowice.flocke.andotp.Preferences;
import android.app.AlertDialog;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.res.TypedArray;
import android.preference.DialogPreference;
import android.support.design.widget.TextInputEditText;
import android.support.design.widget.TextInputLayout;
@ -34,7 +33,6 @@ import android.text.InputType;
import android.text.TextWatcher;
import android.text.method.PasswordTransformationMethod;
import android.util.AttributeSet;
import android.util.Base64;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
@ -45,15 +43,11 @@ import android.widget.ListView;
import android.widget.Toast;
import org.shadowice.flocke.andotp.R;
import org.shadowice.flocke.andotp.Utilities.EncryptionHelper;
import org.shadowice.flocke.andotp.Utilities.Settings;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.List;
import static android.content.Context.AUDIO_SERVICE;
import static android.content.Context.KEYGUARD_SERVICE;
import static org.shadowice.flocke.andotp.Utilities.Constants.AuthMethod;
import static org.shadowice.flocke.andotp.Utilities.Constants.EncryptionType;
@ -62,8 +56,8 @@ public class CredentialsPreference extends DialogPreference
implements AdapterView.OnItemClickListener, View.OnClickListener, TextWatcher {
public static final AuthMethod DEFAULT_VALUE = AuthMethod.NONE;
public interface EncryptionChangeHandler {
void onEncryptionChanged(byte[] newKey);
public interface EncryptionChangeCallback {
boolean testEncryptionChange(byte[] newKey);
}
private List<String> entries;
@ -76,7 +70,7 @@ public class CredentialsPreference extends DialogPreference
private Settings settings;
private AuthMethod value = AuthMethod.NONE;
private EncryptionChangeHandler handler = null;
private EncryptionChangeCallback encryptionChangeCallback = null;
private LinearLayout credentialsLayout;
private TextInputLayout passwordLayout;
@ -94,8 +88,8 @@ public class CredentialsPreference extends DialogPreference
setDialogLayoutResource(R.layout.component_authentication);
}
public void setEncryptionChangeHandler(EncryptionChangeHandler handler) {
this.handler = handler;
public void setEncryptionChangeCallback(EncryptionChangeCallback cb) {
this.encryptionChangeCallback = cb;
}
@Override
@ -184,11 +178,16 @@ public class CredentialsPreference extends DialogPreference
}
}
if (settings.getEncryption() == EncryptionType.PASSWORD) {
if (newKey == null || encryptionChangeCallback == null)
return;
if (! encryptionChangeCallback.testEncryptionChange(newKey))
return;
}
persistString(value.toString().toLowerCase());
setSummary(entries.get(entryValues.indexOf(value)));
if (settings.getEncryption() == EncryptionType.PASSWORD && newKey != null && handler != null)
handler.onEncryptionChanged(newKey);
}
@Override

View file

@ -31,18 +31,63 @@ import org.shadowice.flocke.andotp.Database.Entry;
import org.shadowice.flocke.andotp.R;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import javax.crypto.SecretKey;
public class DatabaseHelper {
public static final String SETTINGS_FILE = "secrets.dat";
public static final String SETTINGS_FILE_BACKUP = "secrets.dat.bck";
public static void wipeDatabase(Context context) {
File db = new File(context.getFilesDir() + "/" + SETTINGS_FILE);
db.delete();
}
private static void copyFile(File src, File dst)
throws IOException {
try (InputStream in = new FileInputStream(src)) {
try (OutputStream out = new FileOutputStream(dst)) {
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
}
}
}
public static boolean backupDatabase(Context context) {
File original = new File(context.getFilesDir() + "/" + SETTINGS_FILE);
File backup = new File(context.getFilesDir() + "/" + SETTINGS_FILE_BACKUP);
try {
copyFile(original, backup);
} catch (IOException e) {
return false;
}
return true;
}
public static boolean restoreDatabaseBackup(Context context) {
File original = new File(context.getFilesDir() + "/" + SETTINGS_FILE);
File backup = new File(context.getFilesDir() + "/" + SETTINGS_FILE_BACKUP);
try {
copyFile(backup, original);
} catch (IOException e) {
return false;
}
return true;
}
/* Database functions */
public static boolean saveDatabase(Context context, ArrayList<Entry> entries, SecretKey encryptionKey) {
if (encryptionKey == null) {

View file

@ -75,6 +75,17 @@
<string name="settings_toast_encryption_invalid_without_credentials">You first need to set a
Password or PIN before changing the encryption!</string>
<string name="settings_toast_encryption_changing">Trying to change the database encryption,
please wait!</string>
<string name="settings_toast_encryption_change_success">Successfully changed the database
encryption!</string>
<string name="settings_toast_encryption_change_failed">Failed to change database encryption,
restored database from internal backup!</string>
<string name="settings_toast_encryption_backup_failed">Failed to create an internal
backup, aborting!</string>
<string name="settings_toast_encryption_no_key">Failed to get the encryption key, aborting!</string>
<string name="settings_toast_encryption_auth_failed">Authentication failed, aborting!</string>
<string name="settings_toast_auth_upgrade_failed">Failed to silently upgrade your password / PIN
to the new encryption, please manually reset it in the settings!</string>