Custom CredentialsPreference and better handling of encryption changes
This commit is contained in:
parent
fc67640730
commit
3565c899b8
22 changed files with 534 additions and 380 deletions
|
@ -48,21 +48,25 @@ import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.spec.InvalidKeySpecException;
|
import java.security.spec.InvalidKeySpecException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import static org.shadowice.flocke.andotp.Utilities.Settings.AuthMethod;
|
import static org.shadowice.flocke.andotp.Utilities.Constants.AuthMethod;
|
||||||
|
|
||||||
public class AuthenticateActivity extends ThemedActivity
|
public class AuthenticateActivity extends ThemedActivity
|
||||||
implements EditText.OnEditorActionListener {
|
implements EditText.OnEditorActionListener {
|
||||||
public static final String EXTRA_NAME_PASSWORD_KEY = "password_key";
|
public static final String AUTH_EXTRA_NAME_PASSWORD_KEY = "password_key";
|
||||||
public static final String EXTRA_NAME_SAVE_DATABASE = "save_database";
|
public static final String AUTH_EXTRA_NAME_FATAL = "fatal";
|
||||||
public static final String EXTRA_NAME_MESSAGE = "message";
|
public static final String AUTH_EXTRA_NAME_SAVE_DATABASE = "save_database";
|
||||||
|
public static final String AUTH_EXTRA_NAME_MESSAGE = "message";
|
||||||
|
|
||||||
boolean saveDatabase = false;
|
boolean saveDatabase = false;
|
||||||
|
boolean fatal = true;
|
||||||
|
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
AuthMethod authMethod;
|
AuthMethod authMethod;
|
||||||
boolean oldPassword = false;
|
boolean oldPassword = false;
|
||||||
|
|
||||||
|
TextInputEditText passwordInput;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
@ -79,12 +83,13 @@ public class AuthenticateActivity extends ThemedActivity
|
||||||
View v = stub.inflate();
|
View v = stub.inflate();
|
||||||
|
|
||||||
Intent callingIntent = getIntent();
|
Intent callingIntent = getIntent();
|
||||||
saveDatabase = callingIntent.getBooleanExtra(EXTRA_NAME_SAVE_DATABASE, false);
|
int labelMsg = callingIntent.getIntExtra(AUTH_EXTRA_NAME_MESSAGE, R.string.auth_msg_authenticate);
|
||||||
int labelMsg = callingIntent.getIntExtra(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);
|
||||||
|
|
||||||
TextView passwordLabel = v.findViewById(R.id.passwordLabel);
|
TextView passwordLabel = v.findViewById(R.id.passwordLabel);
|
||||||
TextInputLayout passwordLayout = v.findViewById(R.id.passwordLayout);
|
TextInputLayout passwordLayout = v.findViewById(R.id.passwordLayout);
|
||||||
TextInputEditText passwordInput = v.findViewById(R.id.passwordEdit);
|
passwordInput = v.findViewById(R.id.passwordEdit);
|
||||||
|
|
||||||
passwordLabel.setText(labelMsg);
|
passwordLabel.setText(labelMsg);
|
||||||
|
|
||||||
|
@ -191,17 +196,26 @@ public class AuthenticateActivity extends ThemedActivity
|
||||||
|
|
||||||
// End with a result
|
// End with a result
|
||||||
public void finishWithResult(boolean success, byte[] key) {
|
public void finishWithResult(boolean success, byte[] key) {
|
||||||
Intent data = new Intent();
|
if (success || fatal) {
|
||||||
|
Intent data = new Intent();
|
||||||
|
|
||||||
data.putExtra(EXTRA_NAME_SAVE_DATABASE, saveDatabase);
|
data.putExtra(AUTH_EXTRA_NAME_SAVE_DATABASE, saveDatabase);
|
||||||
|
|
||||||
if (key != null)
|
if (key != null)
|
||||||
data.putExtra(EXTRA_NAME_PASSWORD_KEY, key);
|
data.putExtra(AUTH_EXTRA_NAME_PASSWORD_KEY, key);
|
||||||
|
|
||||||
if (success)
|
if (success)
|
||||||
setResult(RESULT_OK, data);
|
setResult(RESULT_OK, data);
|
||||||
|
|
||||||
finish();
|
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
|
// Go back to the main activity
|
||||||
|
|
|
@ -58,10 +58,8 @@ import com.google.zxing.integration.android.IntentResult;
|
||||||
|
|
||||||
import org.shadowice.flocke.andotp.Database.Entry;
|
import org.shadowice.flocke.andotp.Database.Entry;
|
||||||
import org.shadowice.flocke.andotp.R;
|
import org.shadowice.flocke.andotp.R;
|
||||||
import org.shadowice.flocke.andotp.Utilities.Constants;
|
|
||||||
import org.shadowice.flocke.andotp.Utilities.EncryptionHelper;
|
import org.shadowice.flocke.andotp.Utilities.EncryptionHelper;
|
||||||
import org.shadowice.flocke.andotp.Utilities.KeyStoreHelper;
|
import org.shadowice.flocke.andotp.Utilities.KeyStoreHelper;
|
||||||
import org.shadowice.flocke.andotp.Utilities.Settings;
|
|
||||||
import org.shadowice.flocke.andotp.Utilities.TokenCalculator;
|
import org.shadowice.flocke.andotp.Utilities.TokenCalculator;
|
||||||
import org.shadowice.flocke.andotp.View.EntriesCardAdapter;
|
import org.shadowice.flocke.andotp.View.EntriesCardAdapter;
|
||||||
import org.shadowice.flocke.andotp.View.FloatingActionMenu;
|
import org.shadowice.flocke.andotp.View.FloatingActionMenu;
|
||||||
|
@ -74,10 +72,15 @@ import java.util.HashMap;
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
|
|
||||||
import static org.shadowice.flocke.andotp.Activities.AuthenticateActivity.EXTRA_NAME_MESSAGE;
|
import static org.shadowice.flocke.andotp.Activities.AuthenticateActivity.AUTH_EXTRA_NAME_FATAL;
|
||||||
import static org.shadowice.flocke.andotp.Activities.AuthenticateActivity.EXTRA_NAME_PASSWORD_KEY;
|
import static org.shadowice.flocke.andotp.Activities.AuthenticateActivity.AUTH_EXTRA_NAME_MESSAGE;
|
||||||
import static org.shadowice.flocke.andotp.Activities.AuthenticateActivity.EXTRA_NAME_SAVE_DATABASE;
|
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.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;
|
||||||
|
import static org.shadowice.flocke.andotp.Utilities.Constants.EncryptionType;
|
||||||
import static org.shadowice.flocke.andotp.Utilities.Settings.SortMode;
|
import static org.shadowice.flocke.andotp.Utilities.Settings.SortMode;
|
||||||
|
|
||||||
public class MainActivity extends BaseActivity
|
public class MainActivity extends BaseActivity
|
||||||
|
@ -92,7 +95,7 @@ public class MainActivity extends BaseActivity
|
||||||
private MenuItem sortMenu;
|
private MenuItem sortMenu;
|
||||||
private SimpleItemTouchHelperCallback touchHelperCallback;
|
private SimpleItemTouchHelperCallback touchHelperCallback;
|
||||||
|
|
||||||
private Constants.EncryptionType encryptionType = Constants.EncryptionType.KEYSTORE;
|
private EncryptionType encryptionType = EncryptionType.KEYSTORE;
|
||||||
private boolean requireAuthentication = false;
|
private boolean requireAuthentication = false;
|
||||||
|
|
||||||
private Handler handler;
|
private Handler handler;
|
||||||
|
@ -127,19 +130,20 @@ public class MainActivity extends BaseActivity
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void authenticate(int messageId, boolean saveDatabase) {
|
public void authenticate(int messageId, boolean saveDatabase, boolean fatal) {
|
||||||
Settings.AuthMethod authMethod = settings.getAuthMethod();
|
AuthMethod authMethod = settings.getAuthMethod();
|
||||||
|
|
||||||
if (authMethod == Settings.AuthMethod.DEVICE) {
|
if (authMethod == AuthMethod.DEVICE) {
|
||||||
KeyguardManager km = (KeyguardManager) getSystemService(KEYGUARD_SERVICE);
|
KeyguardManager km = (KeyguardManager) getSystemService(KEYGUARD_SERVICE);
|
||||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP && km.isKeyguardSecure()) {
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP && km.isKeyguardSecure()) {
|
||||||
Intent authIntent = km.createConfirmDeviceCredentialIntent(getString(R.string.dialog_title_auth), getString(R.string.dialog_msg_auth));
|
Intent authIntent = km.createConfirmDeviceCredentialIntent(getString(R.string.dialog_title_auth), getString(R.string.dialog_msg_auth));
|
||||||
startActivityForResult(authIntent, INTENT_INTERNAL_AUTHENTICATE);
|
startActivityForResult(authIntent, INTENT_INTERNAL_AUTHENTICATE);
|
||||||
}
|
}
|
||||||
} else if (authMethod == Settings.AuthMethod.PASSWORD || authMethod == Settings.AuthMethod.PIN) {
|
} else if (authMethod == AuthMethod.PASSWORD || authMethod == AuthMethod.PIN) {
|
||||||
Intent authIntent = new Intent(this, AuthenticateActivity.class);
|
Intent authIntent = new Intent(this, AuthenticateActivity.class);
|
||||||
authIntent.putExtra(EXTRA_NAME_SAVE_DATABASE, saveDatabase);
|
authIntent.putExtra(AUTH_EXTRA_NAME_SAVE_DATABASE, saveDatabase);
|
||||||
authIntent.putExtra(EXTRA_NAME_MESSAGE, messageId);
|
authIntent.putExtra(AUTH_EXTRA_NAME_FATAL, fatal);
|
||||||
|
authIntent.putExtra(AUTH_EXTRA_NAME_MESSAGE, messageId);
|
||||||
startActivityForResult(authIntent, INTENT_INTERNAL_AUTHENTICATE);
|
startActivityForResult(authIntent, INTENT_INTERNAL_AUTHENTICATE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -198,13 +202,13 @@ public class MainActivity extends BaseActivity
|
||||||
|
|
||||||
encryptionType = settings.getEncryption();
|
encryptionType = settings.getEncryption();
|
||||||
|
|
||||||
if (settings.getAuthMethod() != Settings.AuthMethod.NONE && savedInstanceState == null)
|
if (settings.getAuthMethod() != AuthMethod.NONE && savedInstanceState == null)
|
||||||
requireAuthentication = true;
|
requireAuthentication = true;
|
||||||
|
|
||||||
setBroadcastCallback(new BroadcastReceivedCallback() {
|
setBroadcastCallback(new BroadcastReceivedCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void onReceivedScreenOff() {
|
public void onReceivedScreenOff() {
|
||||||
if (settings.getAuthMethod() != Settings.AuthMethod.NONE)
|
if (settings.getAuthMethod() != AuthMethod.NONE)
|
||||||
requireAuthentication = true;
|
requireAuthentication = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -318,20 +322,20 @@ public class MainActivity extends BaseActivity
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
if (requireAuthentication) {
|
if (requireAuthentication) {
|
||||||
if (settings.getAuthMethod() != Settings.AuthMethod.NONE) {
|
if (settings.getAuthMethod() != AuthMethod.NONE) {
|
||||||
requireAuthentication = false;
|
requireAuthentication = false;
|
||||||
authenticate(R.string.auth_msg_authenticate, false);
|
authenticate(R.string.auth_msg_authenticate,false, true);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (encryptionType == Constants.EncryptionType.KEYSTORE) {
|
if (encryptionType == EncryptionType.KEYSTORE) {
|
||||||
if (adapter.getEncryptionKey() == null) {
|
if (adapter.getEncryptionKey() == null) {
|
||||||
adapter.setEncryptionKey(KeyStoreHelper.loadEncryptionKeyFromKeyStore(this));
|
adapter.setEncryptionKey(KeyStoreHelper.loadEncryptionKeyFromKeyStore(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
populateAdapter();
|
populateAdapter();
|
||||||
} else if (encryptionType == Constants.EncryptionType.PASSWORD) {
|
} else if (encryptionType == EncryptionType.PASSWORD) {
|
||||||
if (adapter.getEncryptionKey() == null) {
|
if (adapter.getEncryptionKey() == null) {
|
||||||
authenticate(R.string.auth_msg_authenticate,false);
|
authenticate(R.string.auth_msg_authenticate,false, true);
|
||||||
} else {
|
} else {
|
||||||
populateAdapter();
|
populateAdapter();
|
||||||
}
|
}
|
||||||
|
@ -358,20 +362,21 @@ public class MainActivity extends BaseActivity
|
||||||
key.equals(getString(R.string.settings_key_lang)) ||
|
key.equals(getString(R.string.settings_key_lang)) ||
|
||||||
key.equals(getString(R.string.settings_key_enable_screenshot))) {
|
key.equals(getString(R.string.settings_key_enable_screenshot))) {
|
||||||
recreate();
|
recreate();
|
||||||
} else if (key.equals(getString(R.string.settings_key_encryption))) {
|
// } else if (key.equals(getString(R.string.settings_key_encryption))) {
|
||||||
if (settings.getEncryption() == Constants.EncryptionType.KEYSTORE) {
|
// if (settings.getEncryption() == EncryptionType.KEYSTORE) {
|
||||||
encryptionType = Constants.EncryptionType.KEYSTORE;
|
// encryptionType = EncryptionType.KEYSTORE;
|
||||||
adapter.setEncryptionKey(KeyStoreHelper.loadEncryptionKeyFromKeyStore(this));
|
// adapter.setEncryptionKey(KeyStoreHelper.loadEncryptionKeyFromKeyStore(this));
|
||||||
adapter.saveEntries();
|
// adapter.saveEntries();
|
||||||
} else if (settings.getEncryption() == Constants.EncryptionType.PASSWORD) {
|
// } else if (settings.getEncryption() == EncryptionType.PASSWORD) {
|
||||||
encryptionType = Constants.EncryptionType.PASSWORD;
|
// encryptionType = EncryptionType.PASSWORD;
|
||||||
authenticate(R.string.auth_msg_confirm_encryption,true);
|
// authenticate(R.string.auth_msg_confirm_encryption,true);
|
||||||
}
|
// }
|
||||||
} else if (key.equals(getString(R.string.settings_key_auth)) ||
|
// } else if (key.equals(getString(R.string.settings_key_auth)) ||
|
||||||
key.equals(getString(R.string.settings_key_auth_password_pbkdf2)) ||
|
// key.equals(getString(R.string.settings_key_auth_password_pbkdf2)) ||
|
||||||
key.equals(getString(R.string.settings_key_auth_pin_pbkdf2))) {
|
// key.equals(getString(R.string.settings_key_auth_pin_pbkdf2))) {
|
||||||
if (encryptionType == Constants.EncryptionType.PASSWORD)
|
// if (encryptionType == EncryptionType.PASSWORD) {
|
||||||
authenticate(R.string.auth_msg_confirm_encryption, true);
|
// authenticate(R.string.auth_msg_confirm_encryption,true);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -404,9 +409,16 @@ public class MainActivity extends BaseActivity
|
||||||
adapter.loadEntries();
|
adapter.loadEntries();
|
||||||
refreshTags();
|
refreshTags();
|
||||||
}
|
}
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
} else if (requestCode == INTENT_INTERNAL_AUTHENTICATE) {
|
} else if (requestCode == INTENT_INTERNAL_AUTHENTICATE) {
|
||||||
if (resultCode != RESULT_OK) {
|
if (resultCode != RESULT_OK) {
|
||||||
Toast.makeText(getBaseContext(), R.string.toast_auth_failed, Toast.LENGTH_LONG).show();
|
Toast.makeText(getBaseContext(), R.string.toast_auth_failed_fatal, Toast.LENGTH_LONG).show();
|
||||||
|
|
||||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
|
||||||
finishAndRemoveTask();
|
finishAndRemoveTask();
|
||||||
|
@ -416,30 +428,39 @@ public class MainActivity extends BaseActivity
|
||||||
} else {
|
} else {
|
||||||
requireAuthentication = false;
|
requireAuthentication = false;
|
||||||
|
|
||||||
SecretKey encryptionKey = null;
|
boolean saveDatabase = intent.getBooleanExtra(AUTH_EXTRA_NAME_SAVE_DATABASE, false);
|
||||||
|
byte[] authKey = intent.getByteArrayExtra(AUTH_EXTRA_NAME_PASSWORD_KEY);
|
||||||
|
|
||||||
if (encryptionType == Constants.EncryptionType.KEYSTORE) {
|
updateEncryption(authKey, saveDatabase);
|
||||||
encryptionKey = KeyStoreHelper.loadEncryptionKeyFromKeyStore(this);
|
|
||||||
} else if (encryptionType == Constants.EncryptionType.PASSWORD) {
|
|
||||||
byte[] credentialSeed = intent.getByteArrayExtra(EXTRA_NAME_PASSWORD_KEY);
|
|
||||||
if (credentialSeed != null && credentialSeed.length > 0)
|
|
||||||
encryptionKey = EncryptionHelper.generateSymmetricKey(credentialSeed);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean saveDatabase = intent.getBooleanExtra(EXTRA_NAME_SAVE_DATABASE, false);
|
|
||||||
|
|
||||||
if (encryptionKey != null) {
|
|
||||||
adapter.setEncryptionKey(encryptionKey);
|
|
||||||
|
|
||||||
if (saveDatabase)
|
|
||||||
adapter.saveEntries();
|
|
||||||
}
|
|
||||||
|
|
||||||
populateAdapter();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateEncryption(byte[] newKey, boolean saveDatabase) {
|
||||||
|
SecretKey encryptionKey = null;
|
||||||
|
|
||||||
|
encryptionType = settings.getEncryption();
|
||||||
|
|
||||||
|
if (encryptionType == EncryptionType.KEYSTORE) {
|
||||||
|
encryptionKey = KeyStoreHelper.loadEncryptionKeyFromKeyStore(this);
|
||||||
|
} else if (encryptionType == EncryptionType.PASSWORD) {
|
||||||
|
if (newKey != null && newKey.length > 0) {
|
||||||
|
encryptionKey = EncryptionHelper.generateSymmetricKey(newKey);
|
||||||
|
} else {
|
||||||
|
authenticate(R.string.auth_msg_confirm_encryption, true, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (encryptionKey != null) {
|
||||||
|
adapter.setEncryptionKey(encryptionKey);
|
||||||
|
|
||||||
|
if (saveDatabase)
|
||||||
|
adapter.saveEntries();
|
||||||
|
}
|
||||||
|
|
||||||
|
populateAdapter();
|
||||||
|
}
|
||||||
|
|
||||||
// Options menu
|
// Options menu
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
|
|
@ -22,7 +22,6 @@
|
||||||
|
|
||||||
package org.shadowice.flocke.andotp.Activities;
|
package org.shadowice.flocke.andotp.Activities;
|
||||||
|
|
||||||
import android.app.KeyguardManager;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
@ -37,14 +36,18 @@ import android.widget.Toast;
|
||||||
|
|
||||||
import org.openintents.openpgp.util.OpenPgpAppPreference;
|
import org.openintents.openpgp.util.OpenPgpAppPreference;
|
||||||
import org.openintents.openpgp.util.OpenPgpKeyPreference;
|
import org.openintents.openpgp.util.OpenPgpKeyPreference;
|
||||||
import org.shadowice.flocke.andotp.Preferences.PBKDF2PasswordPreference;
|
import org.shadowice.flocke.andotp.Preferences.CredentialsPreference;
|
||||||
import org.shadowice.flocke.andotp.R;
|
import org.shadowice.flocke.andotp.R;
|
||||||
import org.shadowice.flocke.andotp.Utilities.Constants;
|
|
||||||
import org.shadowice.flocke.andotp.Utilities.KeyStoreHelper;
|
import org.shadowice.flocke.andotp.Utilities.KeyStoreHelper;
|
||||||
import org.shadowice.flocke.andotp.Utilities.Settings;
|
|
||||||
|
import static org.shadowice.flocke.andotp.Utilities.Constants.AuthMethod;
|
||||||
|
import static org.shadowice.flocke.andotp.Utilities.Constants.EncryptionType;
|
||||||
|
|
||||||
public class SettingsActivity extends BaseActivity
|
public class SettingsActivity extends BaseActivity
|
||||||
implements SharedPreferences.OnSharedPreferenceChangeListener{
|
implements SharedPreferences.OnSharedPreferenceChangeListener{
|
||||||
|
public static final String SETTINGS_EXTRA_NAME_ENCRYPTION_CHANGED = "encryption_changed";
|
||||||
|
public static final String SETTINGS_EXTRA_NAME_ENCRYPTION_KEY = "encryption_key";
|
||||||
|
|
||||||
SettingsFragment fragment;
|
SettingsFragment fragment;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -70,20 +73,27 @@ public class SettingsActivity extends BaseActivity
|
||||||
sharedPref.registerOnSharedPreferenceChangeListener(this);
|
sharedPref.registerOnSharedPreferenceChangeListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void finishWithResult() {
|
public void finishWithResult(boolean encryptionChanged, byte[] newKey) {
|
||||||
setResult(RESULT_OK);
|
Intent data = new Intent();
|
||||||
|
|
||||||
|
data.putExtra(SETTINGS_EXTRA_NAME_ENCRYPTION_CHANGED, encryptionChanged);
|
||||||
|
|
||||||
|
if (newKey != null)
|
||||||
|
data.putExtra(SETTINGS_EXTRA_NAME_ENCRYPTION_KEY, newKey);
|
||||||
|
|
||||||
|
setResult(RESULT_OK, data);
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onSupportNavigateUp() {
|
public boolean onSupportNavigateUp() {
|
||||||
finishWithResult();
|
finishWithResult(false,null);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
finishWithResult();
|
finishWithResult(false, null);
|
||||||
super.onBackPressed();
|
super.onBackPressed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,100 +122,42 @@ public class SettingsActivity extends BaseActivity
|
||||||
OpenPgpAppPreference pgpProvider;
|
OpenPgpAppPreference pgpProvider;
|
||||||
OpenPgpKeyPreference pgpKey;
|
OpenPgpKeyPreference pgpKey;
|
||||||
|
|
||||||
public void updateAuthPassword(Settings.AuthMethod newAuth) {
|
|
||||||
PBKDF2PasswordPreference pwPref = (PBKDF2PasswordPreference) catSecurity.findPreference(getString(R.string.settings_key_auth_password_pbkdf2));
|
|
||||||
PBKDF2PasswordPreference pinPref = (PBKDF2PasswordPreference) catSecurity.findPreference(getString(R.string.settings_key_auth_pin_pbkdf2));
|
|
||||||
|
|
||||||
if (pwPref != null)
|
|
||||||
catSecurity.removePreference(pwPref);
|
|
||||||
if (pinPref != null)
|
|
||||||
catSecurity.removePreference(pinPref);
|
|
||||||
|
|
||||||
if (newAuth == Settings.AuthMethod.PASSWORD) {
|
|
||||||
PBKDF2PasswordPreference authPassword = new PBKDF2PasswordPreference(getActivity(), null);
|
|
||||||
authPassword.setTitle(R.string.settings_title_auth_password);
|
|
||||||
authPassword.setOrder(4);
|
|
||||||
authPassword.setKey(getString(R.string.settings_key_auth_password_pbkdf2));
|
|
||||||
authPassword.setMode(PBKDF2PasswordPreference.Mode.PASSWORD);
|
|
||||||
|
|
||||||
catSecurity.addPreference(authPassword);
|
|
||||||
} else if (newAuth == Settings.AuthMethod.PIN) {
|
|
||||||
PBKDF2PasswordPreference authPIN = new PBKDF2PasswordPreference(getActivity(), null);
|
|
||||||
authPIN.setTitle(R.string.settings_title_auth_pin);
|
|
||||||
authPIN.setOrder(4);
|
|
||||||
authPIN.setKey(getString(R.string.settings_key_auth_pin_pbkdf2));
|
|
||||||
authPIN.setMode(PBKDF2PasswordPreference.Mode.PIN);
|
|
||||||
|
|
||||||
catSecurity.addPreference(authPIN);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
final SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getActivity().getBaseContext());
|
final SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getActivity().getBaseContext());
|
||||||
|
|
||||||
addPreferencesFromResource(R.xml.preferences);
|
addPreferencesFromResource(R.xml.preferences);
|
||||||
|
|
||||||
|
CredentialsPreference credentialsPreference = (CredentialsPreference) findPreference(getString(R.string.settings_key_auth));
|
||||||
|
credentialsPreference.setEncryptionChangeHandler(new CredentialsPreference.EncryptionChangeHandler() {
|
||||||
|
@Override
|
||||||
|
public void onEncryptionChanged(byte[] newKey) {
|
||||||
|
((SettingsActivity) getActivity()).finishWithResult(true, newKey);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Authentication
|
// Authentication
|
||||||
catSecurity = (PreferenceCategory) findPreference(getString(R.string.settings_key_cat_security));
|
catSecurity = (PreferenceCategory) findPreference(getString(R.string.settings_key_cat_security));
|
||||||
ListPreference authPref = (ListPreference) findPreference(getString(R.string.settings_key_auth));
|
|
||||||
encryption = (ListPreference) findPreference(getString(R.string.settings_key_encryption));
|
encryption = (ListPreference) findPreference(getString(R.string.settings_key_encryption));
|
||||||
|
|
||||||
updateAuthPassword(Settings.AuthMethod.valueOf(authPref.getValue().toUpperCase()));
|
|
||||||
|
|
||||||
authPref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onPreferenceChange(Preference preference, Object o) {
|
|
||||||
String newAuth = (String) o;
|
|
||||||
String encryption = sharedPref.getString(getString(R.string.settings_key_encryption), getString(R.string.settings_default_encryption));
|
|
||||||
|
|
||||||
Constants.EncryptionType encryptionType = Constants.EncryptionType.valueOf(encryption.toUpperCase());
|
|
||||||
Settings.AuthMethod authMethod = Settings.AuthMethod.valueOf(newAuth.toUpperCase());
|
|
||||||
|
|
||||||
if (encryptionType == Constants.EncryptionType.PASSWORD) {
|
|
||||||
if (authMethod == Settings.AuthMethod.NONE || authMethod == Settings.AuthMethod.DEVICE) {
|
|
||||||
Toast.makeText(getActivity(), R.string.settings_toast_auth_invalid_with_encryption, Toast.LENGTH_LONG).show();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authMethod == Settings.AuthMethod.DEVICE) {
|
|
||||||
KeyguardManager km = (KeyguardManager) getActivity().getSystemService(KEYGUARD_SERVICE);
|
|
||||||
|
|
||||||
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) {
|
|
||||||
Toast.makeText(getActivity(), R.string.settings_toast_auth_device_pre_lollipop, Toast.LENGTH_LONG).show();
|
|
||||||
return false;
|
|
||||||
} else if (! km.isKeyguardSecure()) {
|
|
||||||
Toast.makeText(getActivity(), R.string.settings_toast_auth_device_not_secure, Toast.LENGTH_LONG).show();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateAuthPassword(authMethod);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
encryption.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
encryption.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreferenceChange(final Preference preference, Object o) {
|
public boolean onPreferenceChange(final Preference preference, Object o) {
|
||||||
String newEncryption = (String) o;
|
String newEncryption = (String) o;
|
||||||
String auth = sharedPref.getString(getString(R.string.settings_key_auth), getString(R.string.settings_default_auth));
|
String auth = sharedPref.getString(getString(R.string.settings_key_auth), CredentialsPreference.DEFAULT_VALUE.name().toLowerCase());
|
||||||
Constants.EncryptionType encryptionType = Constants.EncryptionType.valueOf(newEncryption.toUpperCase());
|
EncryptionType encryptionType = EncryptionType.valueOf(newEncryption.toUpperCase());
|
||||||
Settings.AuthMethod authMethod = Settings.AuthMethod.valueOf(auth.toUpperCase());
|
AuthMethod authMethod = AuthMethod.valueOf(auth.toUpperCase());
|
||||||
|
|
||||||
if (encryptionType == Constants.EncryptionType.PASSWORD) {
|
if (encryptionType == EncryptionType.PASSWORD) {
|
||||||
if (authMethod != Settings.AuthMethod.PASSWORD && authMethod != Settings.AuthMethod.PIN) {
|
if (authMethod != AuthMethod.PASSWORD && authMethod != AuthMethod.PIN) {
|
||||||
Toast.makeText(getActivity(), R.string.settings_toast_encryption_invalid_with_auth, Toast.LENGTH_LONG).show();
|
Toast.makeText(getActivity(), R.string.settings_toast_encryption_invalid_with_auth, Toast.LENGTH_LONG).show();
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
String credentials = "";
|
String credentials = "";
|
||||||
if (authMethod == Settings.AuthMethod.PASSWORD)
|
if (authMethod == AuthMethod.PASSWORD)
|
||||||
credentials = sharedPref.getString(getString(R.string.settings_key_auth_password_pbkdf2), "");
|
credentials = sharedPref.getString(getString(R.string.settings_key_auth_password_pbkdf2), "");
|
||||||
else if (authMethod == Settings.AuthMethod.PIN)
|
else if (authMethod == AuthMethod.PIN)
|
||||||
credentials = sharedPref.getString(getString(R.string.settings_key_auth_pin_pbkdf2), "");
|
credentials = sharedPref.getString(getString(R.string.settings_key_auth_pin_pbkdf2), "");
|
||||||
|
|
||||||
if (credentials.isEmpty()) {
|
if (credentials.isEmpty()) {
|
||||||
|
@ -218,6 +170,7 @@ public class SettingsActivity extends BaseActivity
|
||||||
}
|
}
|
||||||
|
|
||||||
encryption.setValue(newEncryption);
|
encryption.setValue(newEncryption);
|
||||||
|
((SettingsActivity) getActivity()).finishWithResult(true, null);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,271 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2017 Jakob Nixdorf
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
||||||
|
import android.text.Editable;
|
||||||
|
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;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
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;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> entries;
|
||||||
|
private static final List<AuthMethod> entryValues = Arrays.asList(
|
||||||
|
AuthMethod.NONE,
|
||||||
|
AuthMethod.PASSWORD,
|
||||||
|
AuthMethod.PIN,
|
||||||
|
AuthMethod.DEVICE
|
||||||
|
);
|
||||||
|
|
||||||
|
private Settings settings;
|
||||||
|
private AuthMethod value = AuthMethod.NONE;
|
||||||
|
private EncryptionChangeHandler handler = null;
|
||||||
|
|
||||||
|
private LinearLayout credentialsLayout;
|
||||||
|
private TextInputLayout passwordLayout;
|
||||||
|
private TextInputEditText passwordInput;
|
||||||
|
private EditText passwordConfirm;
|
||||||
|
|
||||||
|
private Button btnSave;
|
||||||
|
|
||||||
|
public CredentialsPreference(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
|
||||||
|
settings = new Settings(context);
|
||||||
|
entries = Arrays.asList(context.getResources().getStringArray(R.array.settings_entries_auth));
|
||||||
|
|
||||||
|
setDialogLayoutResource(R.layout.component_authentication);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEncryptionChangeHandler(EncryptionChangeHandler handler) {
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
||||||
|
super.onPrepareDialogBuilder(builder);
|
||||||
|
|
||||||
|
builder.setPositiveButton(null, null);
|
||||||
|
builder.setNegativeButton(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onBindDialogView(View view) {
|
||||||
|
value = settings.getAuthMethod();
|
||||||
|
|
||||||
|
ListView listView = view.findViewById(R.id.credentialSelection);
|
||||||
|
|
||||||
|
ArrayAdapter<String> adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_list_item_single_choice, entries);
|
||||||
|
listView.setAdapter(adapter);
|
||||||
|
|
||||||
|
int index = entryValues.indexOf(value);
|
||||||
|
listView.setSelection(index);
|
||||||
|
listView.setItemChecked(index,true);
|
||||||
|
listView.setOnItemClickListener(this);
|
||||||
|
|
||||||
|
credentialsLayout = view.findViewById(R.id.credentialsLayout);
|
||||||
|
|
||||||
|
passwordLayout = view.findViewById(R.id.passwordLayout);
|
||||||
|
passwordInput = view.findViewById(R.id.passwordEdit);
|
||||||
|
passwordConfirm = view.findViewById(R.id.passwordConfirm);
|
||||||
|
|
||||||
|
passwordInput.addTextChangedListener(this);
|
||||||
|
passwordConfirm.addTextChangedListener(this);
|
||||||
|
|
||||||
|
Button btnCancel = view.findViewById(R.id.btnCancel);
|
||||||
|
btnSave = view.findViewById(R.id.btnSave);
|
||||||
|
|
||||||
|
btnCancel.setOnClickListener(this);
|
||||||
|
btnSave.setOnClickListener(this);
|
||||||
|
|
||||||
|
updateLayout();
|
||||||
|
|
||||||
|
super.onBindDialogView(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
|
||||||
|
if (restorePersistedValue) {
|
||||||
|
String stringValue = getPersistedString(DEFAULT_VALUE.name().toLowerCase());
|
||||||
|
value = AuthMethod.valueOf(stringValue.toUpperCase());
|
||||||
|
} else {
|
||||||
|
value = DEFAULT_VALUE;
|
||||||
|
persistString(value.name().toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
setSummary(entries.get(entryValues.indexOf(value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveValues() {
|
||||||
|
byte[] newKey = null;
|
||||||
|
|
||||||
|
if (settings.getEncryption() == EncryptionType.PASSWORD) {
|
||||||
|
if (value == AuthMethod.NONE || value == AuthMethod.DEVICE) {
|
||||||
|
Toast.makeText(getContext(), R.string.settings_toast_auth_invalid_with_encryption, Toast.LENGTH_LONG).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value == AuthMethod.DEVICE) {
|
||||||
|
KeyguardManager km = (KeyguardManager) getContext().getSystemService(KEYGUARD_SERVICE);
|
||||||
|
|
||||||
|
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
Toast.makeText(getContext(), R.string.settings_toast_auth_device_pre_lollipop, Toast.LENGTH_LONG).show();
|
||||||
|
return;
|
||||||
|
} else if (! km.isKeyguardSecure()) {
|
||||||
|
Toast.makeText(getContext(), R.string.settings_toast_auth_device_not_secure, Toast.LENGTH_LONG).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value == AuthMethod.PASSWORD || value == AuthMethod.PIN) {
|
||||||
|
String password = passwordInput.getText().toString();
|
||||||
|
if (!password.isEmpty()) {
|
||||||
|
newKey = settings.setAuthCredentials(value, password);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
persistString(value.toString().toLowerCase());
|
||||||
|
setSummary(entries.get(entryValues.indexOf(value)));
|
||||||
|
|
||||||
|
if (newKey != null && handler != null)
|
||||||
|
handler.onEncryptionChanged(newKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
switch (view.getId()) {
|
||||||
|
case (R.id.btnCancel):
|
||||||
|
getDialog().dismiss();
|
||||||
|
break;
|
||||||
|
case (R.id.btnSave):
|
||||||
|
saveValues();
|
||||||
|
getDialog().dismiss();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||||
|
String password = passwordInput.getEditableText().toString();
|
||||||
|
String confirm = passwordConfirm.getEditableText().toString();
|
||||||
|
|
||||||
|
if (password.equals(confirm) && ! password.isEmpty() && ! confirm.isEmpty()) {
|
||||||
|
btnSave.setEnabled(true);
|
||||||
|
} else {
|
||||||
|
btnSave.setEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateLayout() {
|
||||||
|
if (value == AuthMethod.NONE) {
|
||||||
|
credentialsLayout.setVisibility(View.GONE);
|
||||||
|
btnSave.setEnabled(true);
|
||||||
|
} else if (value == AuthMethod.PASSWORD) {
|
||||||
|
credentialsLayout.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
passwordLayout.setHint(getContext().getString(R.string.settings_hint_password));
|
||||||
|
passwordConfirm.setHint(R.string.settings_hint_password_confirm);
|
||||||
|
|
||||||
|
passwordInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||||
|
passwordConfirm.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||||
|
|
||||||
|
passwordInput.setTransformationMethod(new PasswordTransformationMethod());
|
||||||
|
passwordConfirm.setTransformationMethod(new PasswordTransformationMethod());
|
||||||
|
|
||||||
|
btnSave.setEnabled(false);
|
||||||
|
} else if (value == AuthMethod.PIN) {
|
||||||
|
credentialsLayout.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
passwordLayout.setHint(getContext().getString(R.string.settings_hint_pin));
|
||||||
|
passwordConfirm.setHint(R.string.settings_hint_pin_confirm);
|
||||||
|
|
||||||
|
passwordInput.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
|
||||||
|
passwordConfirm.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
|
||||||
|
|
||||||
|
passwordInput.setTransformationMethod(new PasswordTransformationMethod());
|
||||||
|
passwordConfirm.setTransformationMethod(new PasswordTransformationMethod());
|
||||||
|
|
||||||
|
btnSave.setEnabled(false);
|
||||||
|
} else if (value == AuthMethod.DEVICE) {
|
||||||
|
credentialsLayout.setVisibility(View.GONE);
|
||||||
|
btnSave.setEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
value = entryValues.get(position);
|
||||||
|
updateLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Needed stub functions
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(Editable s) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -1,189 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2017 Jakob Nixdorf
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.shadowice.flocke.andotp.Preferences;
|
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
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;
|
|
||||||
import android.text.Editable;
|
|
||||||
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.Button;
|
|
||||||
import android.widget.EditText;
|
|
||||||
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;
|
|
||||||
|
|
||||||
public class PBKDF2PasswordPreference extends DialogPreference
|
|
||||||
implements View.OnClickListener, TextWatcher {
|
|
||||||
|
|
||||||
public enum Mode {
|
|
||||||
PASSWORD, PIN
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final String DEFAULT_VALUE = "";
|
|
||||||
|
|
||||||
private Settings settings;
|
|
||||||
private Mode mode = Mode.PASSWORD;
|
|
||||||
|
|
||||||
private TextInputEditText passwordInput;
|
|
||||||
private EditText passwordConfirm;
|
|
||||||
|
|
||||||
private Button btnSave;
|
|
||||||
|
|
||||||
private String value = DEFAULT_VALUE;
|
|
||||||
|
|
||||||
public PBKDF2PasswordPreference(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
|
|
||||||
settings = new Settings(context);
|
|
||||||
|
|
||||||
setDialogLayoutResource(R.layout.component_password);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMode(Mode mode) {
|
|
||||||
this.mode = mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void persistEncryptedString(String value) {
|
|
||||||
if (! value.isEmpty()) {
|
|
||||||
try {
|
|
||||||
int iter = EncryptionHelper.generateRandomIterations();
|
|
||||||
EncryptionHelper.PBKDF2Credentials credentials = EncryptionHelper.generatePBKDF2Credentials(value, settings.getSalt(), iter);
|
|
||||||
persistString(Base64.encodeToString(credentials.password, Base64.URL_SAFE));
|
|
||||||
settings.setIterations(Settings.AuthMethod.valueOf(mode.name()), iter);
|
|
||||||
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Toast.makeText(getContext(), R.string.settings_toast_password_empty, Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
|
||||||
super.onPrepareDialogBuilder(builder);
|
|
||||||
|
|
||||||
builder.setPositiveButton(null, null);
|
|
||||||
builder.setNegativeButton(null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onBindDialogView(View view) {
|
|
||||||
TextInputLayout passwordLayout = view.findViewById(R.id.passwordLayout);
|
|
||||||
passwordInput = view.findViewById(R.id.passwordEdit);
|
|
||||||
passwordConfirm = view.findViewById(R.id.passwordConfirm);
|
|
||||||
|
|
||||||
Button btnCancel = view.findViewById(R.id.btnCancel);
|
|
||||||
btnSave = view.findViewById(R.id.btnSave);
|
|
||||||
btnSave.setEnabled(false);
|
|
||||||
|
|
||||||
btnCancel.setOnClickListener(this);
|
|
||||||
btnSave.setOnClickListener(this);
|
|
||||||
|
|
||||||
if (! value.isEmpty()) {
|
|
||||||
passwordInput.setHint(R.string.settings_hint_unchanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mode == Mode.PASSWORD) {
|
|
||||||
passwordLayout.setHint(getContext().getString(R.string.settings_hint_password));
|
|
||||||
passwordConfirm.setHint(R.string.settings_hint_password_confirm);
|
|
||||||
|
|
||||||
passwordInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
|
||||||
passwordConfirm.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
|
||||||
} else if (mode == Mode.PIN) {
|
|
||||||
passwordLayout.setHint(getContext().getString(R.string.settings_hint_pin));
|
|
||||||
passwordConfirm.setHint(R.string.settings_hint_pin_confirm);
|
|
||||||
|
|
||||||
passwordInput.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
|
|
||||||
passwordConfirm.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
|
|
||||||
}
|
|
||||||
|
|
||||||
passwordInput.setTransformationMethod(new PasswordTransformationMethod());
|
|
||||||
passwordConfirm.setTransformationMethod(new PasswordTransformationMethod());
|
|
||||||
|
|
||||||
passwordConfirm.addTextChangedListener(this);
|
|
||||||
passwordInput.addTextChangedListener(this);
|
|
||||||
|
|
||||||
super.onBindDialogView(view);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Object onGetDefaultValue(TypedArray a, int index) {
|
|
||||||
return a.getString(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
|
|
||||||
if (restorePersistedValue) {
|
|
||||||
value = getPersistedString(DEFAULT_VALUE);
|
|
||||||
} else {
|
|
||||||
value = (String) defaultValue;
|
|
||||||
persistEncryptedString(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick(View view) {
|
|
||||||
switch (view.getId()) {
|
|
||||||
case (R.id.btnCancel):
|
|
||||||
getDialog().dismiss();
|
|
||||||
break;
|
|
||||||
case (R.id.btnSave):
|
|
||||||
value = passwordInput.getText().toString();
|
|
||||||
persistEncryptedString(value);
|
|
||||||
|
|
||||||
getDialog().dismiss();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
||||||
if (passwordConfirm.getEditableText().toString().equals(passwordInput.getEditableText().toString())) {
|
|
||||||
btnSave.setEnabled(true);
|
|
||||||
} else {
|
|
||||||
btnSave.setEnabled(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterTextChanged(Editable s) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
|
||||||
}
|
|
|
@ -23,6 +23,10 @@
|
||||||
package org.shadowice.flocke.andotp.Utilities;
|
package org.shadowice.flocke.andotp.Utilities;
|
||||||
|
|
||||||
public class Constants {
|
public class Constants {
|
||||||
|
public enum AuthMethod {
|
||||||
|
NONE, PASSWORD, PIN, DEVICE
|
||||||
|
}
|
||||||
|
|
||||||
public enum EncryptionType {
|
public enum EncryptionType {
|
||||||
KEYSTORE, PASSWORD
|
KEYSTORE, PASSWORD
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import android.preference.PreferenceManager;
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.shadowice.flocke.andotp.Preferences.CredentialsPreference;
|
||||||
import org.shadowice.flocke.andotp.R;
|
import org.shadowice.flocke.andotp.R;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -43,6 +44,8 @@ import java.util.Set;
|
||||||
|
|
||||||
import static org.shadowice.flocke.andotp.Preferences.PasswordEncryptedPreference.KEY_ALIAS;
|
import static org.shadowice.flocke.andotp.Preferences.PasswordEncryptedPreference.KEY_ALIAS;
|
||||||
import static org.shadowice.flocke.andotp.Utilities.EncryptionHelper.PBKDF2_OLD_DEFAULT_ITERATIONS;
|
import static org.shadowice.flocke.andotp.Utilities.EncryptionHelper.PBKDF2_OLD_DEFAULT_ITERATIONS;
|
||||||
|
import static org.shadowice.flocke.andotp.Utilities.Constants.AuthMethod;
|
||||||
|
import static org.shadowice.flocke.andotp.Utilities.Constants.EncryptionType;
|
||||||
|
|
||||||
public class Settings {
|
public class Settings {
|
||||||
private static final String DEFAULT_BACKUP_FOLDER = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "andOTP";
|
private static final String DEFAULT_BACKUP_FOLDER = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "andOTP";
|
||||||
|
@ -50,10 +53,6 @@ public class Settings {
|
||||||
private Context context;
|
private Context context;
|
||||||
private SharedPreferences settings;
|
private SharedPreferences settings;
|
||||||
|
|
||||||
public enum AuthMethod {
|
|
||||||
NONE, PASSWORD, PIN, DEVICE
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum SortMode {
|
public enum SortMode {
|
||||||
UNSORTED, LABEL, LAST_USED
|
UNSORTED, LABEL, LAST_USED
|
||||||
}
|
}
|
||||||
|
@ -232,11 +231,11 @@ public class Settings {
|
||||||
}
|
}
|
||||||
|
|
||||||
public AuthMethod getAuthMethod() {
|
public AuthMethod getAuthMethod() {
|
||||||
String authString = getString(R.string.settings_key_auth, R.string.settings_default_auth);
|
String authString = getString(R.string.settings_key_auth, CredentialsPreference.DEFAULT_VALUE.name().toLowerCase());
|
||||||
return AuthMethod.valueOf(authString.toUpperCase());
|
return AuthMethod.valueOf(authString.toUpperCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAuthPassword() {
|
private String getAuthPassword() {
|
||||||
return getString(R.string.settings_key_auth_password, "");
|
return getString(R.string.settings_key_auth_password, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,7 +255,7 @@ public class Settings {
|
||||||
setString(R.string.settings_key_auth_password_pbkdf2, password);
|
setString(R.string.settings_key_auth_password_pbkdf2, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAuthPIN() {
|
private String getAuthPIN() {
|
||||||
return getString(R.string.settings_key_auth_pin, "");
|
return getString(R.string.settings_key_auth_pin, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,6 +275,29 @@ public class Settings {
|
||||||
setString(R.string.settings_key_auth_pin_pbkdf2, pin);
|
setString(R.string.settings_key_auth_pin_pbkdf2, pin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] setAuthCredentials(AuthMethod method, String plainPassword) {
|
||||||
|
byte[] key = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
int iterations = EncryptionHelper.generateRandomIterations();
|
||||||
|
EncryptionHelper.PBKDF2Credentials credentials = EncryptionHelper.generatePBKDF2Credentials(plainPassword, getSalt(), iterations);
|
||||||
|
String password = Base64.encodeToString(credentials.password, Base64.URL_SAFE);
|
||||||
|
|
||||||
|
setIterations(method, iterations);
|
||||||
|
|
||||||
|
if (method == AuthMethod.PASSWORD)
|
||||||
|
setAuthPasswordPBKDF2(password);
|
||||||
|
else if (method == AuthMethod.PIN)
|
||||||
|
setAuthPINPBKDF2(password);
|
||||||
|
|
||||||
|
key = credentials.key;
|
||||||
|
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
public void setSalt(byte[] bytes) {
|
public void setSalt(byte[] bytes) {
|
||||||
String encodedSalt = Base64.encodeToString(bytes, Base64.URL_SAFE);
|
String encodedSalt = Base64.encodeToString(bytes, Base64.URL_SAFE);
|
||||||
setString(R.string.settings_key_auth_salt, encodedSalt);
|
setString(R.string.settings_key_auth_salt, encodedSalt);
|
||||||
|
@ -310,9 +332,9 @@ public class Settings {
|
||||||
setInt(R.string.settings_key_auth_pin_iter, value);
|
setInt(R.string.settings_key_auth_pin_iter, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Constants.EncryptionType getEncryption() {
|
public EncryptionType getEncryption() {
|
||||||
String encType = getString(R.string.settings_key_encryption, R.string.settings_default_encryption);
|
String encType = getString(R.string.settings_key_encryption, R.string.settings_default_encryption);
|
||||||
return Constants.EncryptionType.valueOf(encType.toUpperCase());
|
return EncryptionType.valueOf(encType.toUpperCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setEncryption(String encryption) {
|
public void setEncryption(String encryption) {
|
||||||
|
|
73
app/src/main/res/layout/component_authentication.xml
Normal file
73
app/src/main/res/layout/component_authentication.xml
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
<?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:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingTop="@dimen/activity_margin_medium"
|
||||||
|
android:paddingBottom="@dimen/activity_margin_small"
|
||||||
|
android:paddingStart="@dimen/activity_margin_medium"
|
||||||
|
android:paddingEnd="@dimen/activity_margin_medium" >
|
||||||
|
|
||||||
|
<ListView
|
||||||
|
android:id="@+id/credentialSelection"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:choiceMode="singleChoice" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/credentialsLayout"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="@dimen/activity_margin"
|
||||||
|
android:paddingEnd="@dimen/activity_margin">
|
||||||
|
|
||||||
|
<android.support.design.widget.TextInputLayout
|
||||||
|
android:id="@+id/passwordLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/settings_hint_password"
|
||||||
|
app:passwordToggleEnabled="true" >
|
||||||
|
|
||||||
|
<android.support.design.widget.TextInputEditText
|
||||||
|
android:id="@+id/passwordEdit"
|
||||||
|
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/settings_hint_password_confirm"
|
||||||
|
android:inputType="textPassword" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<android.support.v7.widget.ButtonBarLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/activity_margin"
|
||||||
|
android:gravity="end" >
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnCancel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="?android:attr/buttonBarButtonStyle"
|
||||||
|
android:text="@string/button_cancel" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnSave"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="?android:attr/buttonBarButtonStyle"
|
||||||
|
android:text="@string/button_save" />
|
||||||
|
|
||||||
|
</android.support.v7.widget.ButtonBarLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -41,7 +41,7 @@
|
||||||
<string name="menu_popup_edit_tags">Upravit štítky</string>
|
<string name="menu_popup_edit_tags">Upravit štítky</string>
|
||||||
<string name="menu_popup_remove">Smazat</string>
|
<string name="menu_popup_remove">Smazat</string>
|
||||||
<!-- Toast messages -->
|
<!-- Toast messages -->
|
||||||
<string name="toast_auth_failed">Ověření se nezdařilo, zavírám andOTP!</string>
|
<string name="toast_auth_failed_fatal">Ověření se nezdařilo, zavírám andOTP!</string>
|
||||||
<string name="toast_copied_to_clipboard">Zkopírováno do schránky</string>
|
<string name="toast_copied_to_clipboard">Zkopírováno do schránky</string>
|
||||||
<string name="toast_entry_exists">Tento záznam již existuje</string>
|
<string name="toast_entry_exists">Tento záznam již existuje</string>
|
||||||
<string name="toast_invalid_qr_code">Neplatný QR kód</string>
|
<string name="toast_invalid_qr_code">Neplatný QR kód</string>
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
<string name="menu_popup_edit_tags">Marker bearbeiten</string>
|
<string name="menu_popup_edit_tags">Marker bearbeiten</string>
|
||||||
<string name="menu_popup_remove">Entfernen</string>
|
<string name="menu_popup_remove">Entfernen</string>
|
||||||
<!-- Toast messages -->
|
<!-- Toast messages -->
|
||||||
<string name="toast_auth_failed">Authentifizierung fehlgeschlagen, andOTP wird geschlossen!</string>
|
<string name="toast_auth_failed_fatal">Authentifizierung fehlgeschlagen, andOTP wird geschlossen!</string>
|
||||||
<string name="toast_copied_to_clipboard">In Zwischenablage kopiert</string>
|
<string name="toast_copied_to_clipboard">In Zwischenablage kopiert</string>
|
||||||
<string name="toast_entry_exists">Dieser Eintrag ist bereits vorhanden</string>
|
<string name="toast_entry_exists">Dieser Eintrag ist bereits vorhanden</string>
|
||||||
<string name="toast_invalid_qr_code">Ungültiger QR-Code</string>
|
<string name="toast_invalid_qr_code">Ungültiger QR-Code</string>
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
<string name="menu_popup_edit_tags">Editar etiquetas</string>
|
<string name="menu_popup_edit_tags">Editar etiquetas</string>
|
||||||
<string name="menu_popup_remove">Eliminar</string>
|
<string name="menu_popup_remove">Eliminar</string>
|
||||||
<!-- Toast messages -->
|
<!-- Toast messages -->
|
||||||
<string name="toast_auth_failed">La autenticación ha fallado, cerrando andOTP!</string>
|
<string name="toast_auth_failed_fatal">La autenticación ha fallado, cerrando andOTP!</string>
|
||||||
<string name="toast_copied_to_clipboard">Copiado al portapapeles</string>
|
<string name="toast_copied_to_clipboard">Copiado al portapapeles</string>
|
||||||
<string name="toast_entry_exists">La entrada ya existe</string>
|
<string name="toast_entry_exists">La entrada ya existe</string>
|
||||||
<string name="toast_invalid_qr_code">Código QR inválido</string>
|
<string name="toast_invalid_qr_code">Código QR inválido</string>
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
<string name="menu_popup_edit_tags">Éditer les tags</string>
|
<string name="menu_popup_edit_tags">Éditer les tags</string>
|
||||||
<string name="menu_popup_remove">Supprimer</string>
|
<string name="menu_popup_remove">Supprimer</string>
|
||||||
<!-- Toast messages -->
|
<!-- Toast messages -->
|
||||||
<string name="toast_auth_failed">
|
<string name="toast_auth_failed_fatal">
|
||||||
L\'authentification a échoué, fermeture d’andOTP !
|
L\'authentification a échoué, fermeture d’andOTP !
|
||||||
</string>
|
</string>
|
||||||
<string name="toast_copied_to_clipboard">
|
<string name="toast_copied_to_clipboard">
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
<string name="menu_popup_edit_tags">Editar etiquetas</string>
|
<string name="menu_popup_edit_tags">Editar etiquetas</string>
|
||||||
<string name="menu_popup_remove">Eliminar</string>
|
<string name="menu_popup_remove">Eliminar</string>
|
||||||
<!-- Toast messages -->
|
<!-- Toast messages -->
|
||||||
<string name="toast_auth_failed">A autenticación fallou, cerrando andOTP!</string>
|
<string name="toast_auth_failed_fatal">A autenticación fallou, cerrando andOTP!</string>
|
||||||
<string name="toast_copied_to_clipboard">Copiado ao portapapeis</string>
|
<string name="toast_copied_to_clipboard">Copiado ao portapapeis</string>
|
||||||
<string name="toast_entry_exists">Este nome xa existe</string>
|
<string name="toast_entry_exists">Este nome xa existe</string>
|
||||||
<string name="toast_invalid_qr_code">Código QR inválido</string>
|
<string name="toast_invalid_qr_code">Código QR inválido</string>
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
<string name="menu_popup_edit_tags">Labels bewerken</string>
|
<string name="menu_popup_edit_tags">Labels bewerken</string>
|
||||||
<string name="menu_popup_remove">Verwijderen</string>
|
<string name="menu_popup_remove">Verwijderen</string>
|
||||||
<!-- Toast messages -->
|
<!-- Toast messages -->
|
||||||
<string name="toast_auth_failed">Verificatie is mislukt, sluiten andOTP!</string>
|
<string name="toast_auth_failed_fatal">Verificatie is mislukt, sluiten andOTP!</string>
|
||||||
<string name="toast_copied_to_clipboard">Gekopieerd naar klembord</string>
|
<string name="toast_copied_to_clipboard">Gekopieerd naar klembord</string>
|
||||||
<string name="toast_entry_exists">Deze item bestaat al</string>
|
<string name="toast_entry_exists">Deze item bestaat al</string>
|
||||||
<string name="toast_invalid_qr_code">Ongeldige QR Code</string>
|
<string name="toast_invalid_qr_code">Ongeldige QR Code</string>
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
<string name="menu_popup_edit_tags">Edytuj znaczniki</string>
|
<string name="menu_popup_edit_tags">Edytuj znaczniki</string>
|
||||||
<string name="menu_popup_remove">Usuń</string>
|
<string name="menu_popup_remove">Usuń</string>
|
||||||
<!-- Toast messages -->
|
<!-- Toast messages -->
|
||||||
<string name="toast_auth_failed">Autoryzacja nie powiodła się, zamykam andOTP!</string>
|
<string name="toast_auth_failed_fatal">Autoryzacja nie powiodła się, zamykam andOTP!</string>
|
||||||
<string name="toast_copied_to_clipboard">Skopiowano do schowka</string>
|
<string name="toast_copied_to_clipboard">Skopiowano do schowka</string>
|
||||||
<string name="toast_entry_exists">Taki rekord już istnieje</string>
|
<string name="toast_entry_exists">Taki rekord już istnieje</string>
|
||||||
<string name="toast_invalid_qr_code">Nieprawidłowy kod QR</string>
|
<string name="toast_invalid_qr_code">Nieprawidłowy kod QR</string>
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
<string name="menu_popup_edit_tags">Изменить теги</string>
|
<string name="menu_popup_edit_tags">Изменить теги</string>
|
||||||
<string name="menu_popup_remove">Убрать</string>
|
<string name="menu_popup_remove">Убрать</string>
|
||||||
<!-- Toast messages -->
|
<!-- Toast messages -->
|
||||||
<string name="toast_auth_failed">Ошибка аутентификации, andOTP закрывается!</string>
|
<string name="toast_auth_failed_fatal">Ошибка аутентификации, andOTP закрывается!</string>
|
||||||
<string name="toast_copied_to_clipboard">Скопировано в буфер обмена</string>
|
<string name="toast_copied_to_clipboard">Скопировано в буфер обмена</string>
|
||||||
<string name="toast_entry_exists">Эта запись уже существует</string>
|
<string name="toast_entry_exists">Эта запись уже существует</string>
|
||||||
<string name="toast_invalid_qr_code">Недопустимый QR-код</string>
|
<string name="toast_invalid_qr_code">Недопустимый QR-код</string>
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
<string name="menu_popup_edit_tags">编辑标签</string>
|
<string name="menu_popup_edit_tags">编辑标签</string>
|
||||||
<string name="menu_popup_remove">移除</string>
|
<string name="menu_popup_remove">移除</string>
|
||||||
<!-- Toast messages -->
|
<!-- Toast messages -->
|
||||||
<string name="toast_auth_failed">身份验证失败,andOTP 正在关闭!</string>
|
<string name="toast_auth_failed_fatal">身份验证失败,andOTP 正在关闭!</string>
|
||||||
<string name="toast_copied_to_clipboard">已复制到剪贴板</string>
|
<string name="toast_copied_to_clipboard">已复制到剪贴板</string>
|
||||||
<string name="toast_entry_exists">该项已存在</string>
|
<string name="toast_entry_exists">该项已存在</string>
|
||||||
<string name="toast_invalid_qr_code">无效二维码</string>
|
<string name="toast_invalid_qr_code">无效二维码</string>
|
||||||
|
|
|
@ -47,7 +47,6 @@
|
||||||
|
|
||||||
<!-- Default values -->
|
<!-- Default values -->
|
||||||
<integer name="settings_default_tap_to_reveal_timeout">30</integer>
|
<integer name="settings_default_tap_to_reveal_timeout">30</integer>
|
||||||
<string name="settings_default_auth" translatable="false">none</string>
|
|
||||||
<string name="settings_default_encryption" translatable="false">keystore</string>
|
<string name="settings_default_encryption" translatable="false">keystore</string>
|
||||||
<string name="settings_default_lang" translatable="false">system</string>
|
<string name="settings_default_lang" translatable="false">system</string>
|
||||||
<string name="settings_default_theme" translatable="false">light</string>
|
<string name="settings_default_theme" translatable="false">light</string>
|
||||||
|
@ -67,13 +66,6 @@
|
||||||
</array>
|
</array>
|
||||||
|
|
||||||
<!-- List values -->
|
<!-- List values -->
|
||||||
<string-array name="settings_values_auth" translatable="false">
|
|
||||||
<item>none</item>
|
|
||||||
<item>password</item>
|
|
||||||
<item>pin</item>
|
|
||||||
<item>device</item>
|
|
||||||
</string-array>
|
|
||||||
|
|
||||||
<string-array name="settings_values_encryption" translatable="false">
|
<string-array name="settings_values_encryption" translatable="false">
|
||||||
<item>keystore</item>
|
<item>keystore</item>
|
||||||
<item>password</item>
|
<item>password</item>
|
||||||
|
|
|
@ -8,10 +8,12 @@
|
||||||
|
|
||||||
<!-- Messages -->
|
<!-- Messages -->
|
||||||
<string name="auth_msg_authenticate">Please authenticate to start andOTP!</string>
|
<string name="auth_msg_authenticate">Please authenticate to start andOTP!</string>
|
||||||
<string name="auth_msg_confirm_encryption">Please confirm your authentication to update the
|
<string name="auth_msg_confirm_encryption">Please confirm your authentication to generate the
|
||||||
encryption key!</string>
|
new encryption key!</string>
|
||||||
|
|
||||||
<!-- Toast messages -->
|
<!-- Toast messages -->
|
||||||
<string name="auth_toast_password_missing">Please set a password in the settings!</string>
|
<string name="auth_toast_password_missing">Please set a password in the settings!</string>
|
||||||
<string name="auth_toast_pin_missing">Please set a PIN in the settings!</string>
|
<string name="auth_toast_pin_missing">Please set a PIN in the settings!</string>
|
||||||
|
<string name="auth_toast_password_again">Wrong password, please try again!</string>
|
||||||
|
<string name="auth_toast_pin_again">Wrong PIN, please try again!</string>
|
||||||
</resources>
|
</resources>
|
|
@ -51,7 +51,8 @@
|
||||||
<string name="menu_popup_remove">Remove</string>
|
<string name="menu_popup_remove">Remove</string>
|
||||||
|
|
||||||
<!-- Toast messages -->
|
<!-- Toast messages -->
|
||||||
<string name="toast_auth_failed">Authentication failed, closing andOTP!</string>
|
<string name="toast_auth_failed">Authentication failed, please try again!</string>
|
||||||
|
<string name="toast_auth_failed_fatal">Authentication failed, closing andOTP!</string>
|
||||||
<string name="toast_copied_to_clipboard">Copied to clipboard</string>
|
<string name="toast_copied_to_clipboard">Copied to clipboard</string>
|
||||||
<string name="toast_entry_exists">This entry already exists</string>
|
<string name="toast_entry_exists">This entry already exists</string>
|
||||||
<string name="toast_invalid_qr_code">Invalid QR Code</string>
|
<string name="toast_invalid_qr_code">Invalid QR Code</string>
|
||||||
|
|
|
@ -113,9 +113,8 @@
|
||||||
<string name="settings_lang_sys_default">System default</string>
|
<string name="settings_lang_sys_default">System default</string>
|
||||||
|
|
||||||
<!-- PasswordPreference -->
|
<!-- PasswordPreference -->
|
||||||
<string name="settings_hint_password">Password</string>
|
<string name="settings_hint_password">Enter new password</string>
|
||||||
<string name="settings_hint_pin">PIN</string>
|
<string name="settings_hint_pin">Enter new PIN</string>
|
||||||
<string name="settings_hint_password_confirm">Confirm password</string>
|
<string name="settings_hint_password_confirm">Confirm password</string>
|
||||||
<string name="settings_hint_pin_confirm">Confirm PIN</string>
|
<string name="settings_hint_pin_confirm">Confirm PIN</string>
|
||||||
<string name="settings_hint_unchanged">(unchanged)</string>
|
|
||||||
</resources>
|
</resources>
|
|
@ -9,14 +9,12 @@
|
||||||
|
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:key="@string/settings_key_tap_to_reveal"
|
android:key="@string/settings_key_tap_to_reveal"
|
||||||
android:order="1"
|
|
||||||
android:title="@string/settings_title_tap_to_reveal"
|
android:title="@string/settings_title_tap_to_reveal"
|
||||||
android:summary="@string/settings_desc_tap_to_reveal"
|
android:summary="@string/settings_desc_tap_to_reveal"
|
||||||
android:defaultValue="false" />
|
android:defaultValue="false" />
|
||||||
|
|
||||||
<com.vanniktech.vntnumberpickerpreference.VNTNumberPickerPreference
|
<com.vanniktech.vntnumberpickerpreference.VNTNumberPickerPreference
|
||||||
android:key="@string/settings_key_tap_to_reveal_timeout"
|
android:key="@string/settings_key_tap_to_reveal_timeout"
|
||||||
android:order="2"
|
|
||||||
android:title="@string/settings_title_tap_to_reveal_timeout"
|
android:title="@string/settings_title_tap_to_reveal_timeout"
|
||||||
android:dialogMessage="@string/settings_desc_tap_to_reveal_timeout"
|
android:dialogMessage="@string/settings_desc_tap_to_reveal_timeout"
|
||||||
android:defaultValue="@integer/settings_default_tap_to_reveal_timeout"
|
android:defaultValue="@integer/settings_default_tap_to_reveal_timeout"
|
||||||
|
@ -24,18 +22,12 @@
|
||||||
app:vnt_minValue="@integer/settings_min_tap_to_reveal_timeout"
|
app:vnt_minValue="@integer/settings_min_tap_to_reveal_timeout"
|
||||||
app:vnt_maxValue="@integer/settings_max_tap_to_reveal_timeout" />
|
app:vnt_maxValue="@integer/settings_max_tap_to_reveal_timeout" />
|
||||||
|
|
||||||
<ListPreference
|
<org.shadowice.flocke.andotp.Preferences.CredentialsPreference
|
||||||
android:key="@string/settings_key_auth"
|
android:key="@string/settings_key_auth"
|
||||||
android:order="3"
|
android:title="@string/settings_title_auth" />
|
||||||
android:title="@string/settings_title_auth"
|
|
||||||
android:summary="%s"
|
|
||||||
android:entries="@array/settings_entries_auth"
|
|
||||||
android:entryValues="@array/settings_values_auth"
|
|
||||||
android:defaultValue="@string/settings_default_auth" />
|
|
||||||
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:key="@string/settings_key_encryption"
|
android:key="@string/settings_key_encryption"
|
||||||
android:order="5"
|
|
||||||
android:title="@string/settings_title_encryption"
|
android:title="@string/settings_title_encryption"
|
||||||
android:summary="%s"
|
android:summary="%s"
|
||||||
android:entries="@array/settings_entries_encryption"
|
android:entries="@array/settings_entries_encryption"
|
||||||
|
@ -44,7 +36,6 @@
|
||||||
|
|
||||||
<MultiSelectListPreference
|
<MultiSelectListPreference
|
||||||
android:key="@string/settings_key_panic"
|
android:key="@string/settings_key_panic"
|
||||||
android:order="6"
|
|
||||||
android:title="@string/settings_title_panic"
|
android:title="@string/settings_title_panic"
|
||||||
android:summary="@string/settings_desc_panic"
|
android:summary="@string/settings_desc_panic"
|
||||||
android:entries="@array/settings_entries_panic"
|
android:entries="@array/settings_entries_panic"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue