Improve the export functionality in numerous ways

This patch improves the export functionality in the following ways:
1. Allow setting a password even if the Aegis vault is not encrypted
2. Display a scary warning when exporting an unencrypted vault
3. Support exporting to a Google Authenticator URI file
4. Option to use Android's share mechanism

<img src="https://alexbakker.me/u/375oh146vz.png" width="300" />
This commit is contained in:
Alexander Bakker 2020-10-25 21:42:26 +01:00
parent 61364b5542
commit 5f2529ea33
27 changed files with 358 additions and 85 deletions

View file

@ -13,6 +13,8 @@ def getCmdOutput = { cmd ->
def getGitHash = { -> return getCmdOutput(["git", "rev-parse", "--short", "HEAD"]) }
def getGitBranch = { -> return getCmdOutput(["git", "rev-parse", "--abbrev-ref", "HEAD"]) }
def fileProviderAuthority = "com.beemdevelopment.aegis.fileprovider"
android {
compileSdkVersion 30
@ -25,6 +27,8 @@ android {
multiDexEnabled true
buildConfigField "String", "GIT_HASH", "\"${getGitHash()}\""
buildConfigField "String", "GIT_BRANCH", "\"${getGitBranch()}\""
buildConfigField("String", "FILE_PROVIDER_AUTHORITY", "\"${fileProviderAuthority}\"")
manifestPlaceholders = [fileProviderAuthority: "${fileProviderAuthority}"]
testInstrumentationRunner "com.beemdevelopment.aegis.AegisTestRunner"
testInstrumentationRunnerArguments clearPackageData: 'true'

View file

@ -7,7 +7,7 @@ import android.preference.PreferenceManager;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.runner.AndroidJUnitRunner;
import java.io.File;
import com.beemdevelopment.aegis.util.IOUtils;
public class AegisTestRunner extends AndroidJUnitRunner {
@Override
@ -15,7 +15,7 @@ public class AegisTestRunner extends AndroidJUnitRunner {
Context context = app.getApplicationContext();
// clear internal storage so that there is no vault file
clearDirectory(context.getFilesDir(), false);
IOUtils.clearDirectory(context.getFilesDir(), false);
// clear preferences so that the intro is started from MainActivity
ApplicationProvider.getApplicationContext().getFilesDir();
@ -26,21 +26,4 @@ public class AegisTestRunner extends AndroidJUnitRunner {
super.callApplicationOnCreate(app);
}
private static void clearDirectory(File dir, boolean deleteParent) {
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
clearDirectory(file, true);
} else {
file.delete();
}
}
}
if (deleteParent) {
dir.delete();
}
}
}

View file

@ -71,10 +71,6 @@
<activity
android:name=".ui.GroupManagerActivity"
android:label="@string/title_activity_manage_groups" />
<activity android:name=".ui.ExitActivity" />
<service android:name=".services.NotificationService" />
<activity
android:name=".ui.PanicResponderActivity"
android:launchMode="singleInstance"
@ -85,6 +81,20 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:name=".ui.ExitActivity" />
<service android:name=".services.NotificationService" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${fileProviderAuthority}"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths">
</meta-data>
</provider>
</application>
</manifest>
</manifest>

View file

@ -21,6 +21,7 @@ import androidx.lifecycle.ProcessLifecycleOwner;
import com.beemdevelopment.aegis.services.NotificationService;
import com.beemdevelopment.aegis.ui.MainActivity;
import com.beemdevelopment.aegis.util.IOUtils;
import com.beemdevelopment.aegis.vault.Vault;
import com.beemdevelopment.aegis.vault.VaultFile;
import com.beemdevelopment.aegis.vault.VaultFileCredentials;
@ -61,6 +62,9 @@ public class AegisApplication extends Application {
// lock the app if the user moves the application to the background
ProcessLifecycleOwner.get().getLifecycle().addObserver(new AppLifecycleObserver());
// clear the cache directory on startup, to make sure no temporary vault export files remain
IOUtils.clearDirectory(getCacheDir(), false);
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
initAppShortcuts();
}

View file

@ -7,13 +7,21 @@ import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.biometric.BiometricPrompt;
import androidx.core.content.FileProvider;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.SwitchPreferenceCompat;
@ -28,6 +36,7 @@ import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
import com.beemdevelopment.aegis.helpers.BiometricSlotInitializer;
import com.beemdevelopment.aegis.helpers.BiometricsHelper;
import com.beemdevelopment.aegis.helpers.SpinnerHelper;
import com.beemdevelopment.aegis.importers.AegisImporter;
import com.beemdevelopment.aegis.importers.DatabaseImporter;
import com.beemdevelopment.aegis.importers.DatabaseImporterEntryException;
@ -50,14 +59,15 @@ import com.beemdevelopment.aegis.vault.slots.SlotException;
import com.beemdevelopment.aegis.vault.slots.SlotList;
import com.topjohnwu.superuser.Shell;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import javax.crypto.Cipher;
@ -71,8 +81,9 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
private static final int CODE_GROUPS = 3;
private static final int CODE_SELECT_ENTRIES = 4;
private static final int CODE_EXPORT = 5;
private static final int CODE_EXPORT_ENCRYPT = 6;
private static final int CODE_BACKUPS = 7;
private static final int CODE_EXPORT_PLAIN = 6;
private static final int CODE_EXPORT_GOOGLE_URI = 7;
private static final int CODE_BACKUPS = 8;
private Intent _result;
private Preferences _prefs;
@ -529,8 +540,10 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
break;
case CODE_EXPORT:
// intentional fallthrough
case CODE_EXPORT_ENCRYPT:
onExportResult(resultCode, data, requestCode == CODE_EXPORT_ENCRYPT);
case CODE_EXPORT_PLAIN:
// intentional fallthrough
case CODE_EXPORT_GOOGLE_URI:
onExportResult(requestCode, resultCode, data);
break;
case CODE_BACKUPS:
onSelectBackupsLocationResult(resultCode, data);
@ -662,30 +675,111 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
}
private void startExport() {
// TODO: create a custom layout to show a message AND a checkbox
final AtomicReference<Boolean> checked = new AtomicReference<>(true);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_export, null);
TextView warningText = view.findViewById(R.id.text_export_warning);
CheckBox checkBoxEncrypt = view.findViewById(R.id.checkbox_export_encrypt);
CheckBox checkBoxAccept = view.findViewById(R.id.checkbox_accept);
Spinner spinner = view.findViewById(R.id.spinner_export_format);
SpinnerHelper.fillSpinner(getContext(), spinner, R.array.export_formats);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
checkBoxEncrypt.setChecked(position == 0);
checkBoxEncrypt.setEnabled(position == 0);
warningText.setVisibility(checkBoxEncrypt.isChecked() ? View.GONE : View.VISIBLE);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
AlertDialog dialog = new AlertDialog.Builder(getContext())
.setTitle(R.string.pref_export_summary)
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
String filename = checked.get() ? VaultManager.FILENAME_PREFIX_EXPORT : VaultManager.FILENAME_PREFIX_EXPORT_PLAIN;
filename = new VaultBackupManager.FileInfo(filename).toString();
.setView(view)
.setNeutralButton(R.string.share, null)
.setPositiveButton(android.R.string.ok, null)
.setNegativeButton(android.R.string.cancel, null)
.create();
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT)
.addCategory(Intent.CATEGORY_OPENABLE)
.setType("application/json")
.putExtra(Intent.EXTRA_TITLE, filename);
dialog.setOnShowListener(d -> {
Button btnPos = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
Button btnNeutral = dialog.getButton(AlertDialog.BUTTON_NEUTRAL);
startActivityForResult(intent, checked.get() ? CODE_EXPORT_ENCRYPT : CODE_EXPORT);
})
.setNegativeButton(android.R.string.cancel, null);
if (_vault.isEncryptionEnabled()) {
final String[] items = {getString(R.string.pref_export_keep_encrypted)};
final boolean[] checkedItems = {true};
builder.setMultiChoiceItems(items, checkedItems, (dialog, index, isChecked) -> checked.set(isChecked));
} else {
builder.setMessage(R.string.export_warning);
}
Dialogs.showSecureDialog(builder.create());
checkBoxEncrypt.setOnCheckedChangeListener((buttonView, isChecked) -> {
warningText.setVisibility(isChecked ? View.GONE : View.VISIBLE);
checkBoxAccept.setVisibility(isChecked ? View.GONE : View.VISIBLE);
checkBoxAccept.setChecked(false);
btnPos.setEnabled(isChecked);
btnNeutral.setEnabled(isChecked);
});
checkBoxAccept.setOnCheckedChangeListener((buttonView, isChecked) -> {
btnPos.setEnabled(isChecked);
btnNeutral.setEnabled(isChecked);
});
btnPos.setOnClickListener(v -> {
dialog.dismiss();
if (!checkBoxEncrypt.isChecked() && !checkBoxAccept.isChecked()) {
return;
}
int requestCode = getExportRequestCode(spinner.getSelectedItemPosition(), checkBoxEncrypt.isChecked());
VaultBackupManager.FileInfo fileInfo = getExportFileInfo(spinner.getSelectedItemPosition(), checkBoxEncrypt.isChecked());
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT)
.addCategory(Intent.CATEGORY_OPENABLE)
.setType(getExportMimeType(requestCode))
.putExtra(Intent.EXTRA_TITLE, fileInfo.toString());
startActivityForResult(intent, requestCode);
});
btnNeutral.setOnClickListener(v -> {
dialog.dismiss();
if (!checkBoxEncrypt.isChecked() && !checkBoxAccept.isChecked()) {
return;
}
File file;
try {
VaultBackupManager.FileInfo fileInfo = getExportFileInfo(spinner.getSelectedItemPosition(), checkBoxEncrypt.isChecked());
File dir = new File(getContext().getCacheDir(), "export");
if (!dir.exists() && !dir.mkdir()) {
throw new IOException(String.format("Unable to create directory %s", dir));
}
file = File.createTempFile(fileInfo.getFilename() + "-", "." + fileInfo.getExtension(), dir);
} catch (IOException e) {
e.printStackTrace();
Dialogs.showErrorDialog(getContext(), R.string.exporting_vault_error, e);
return;
}
int requestCode = getExportRequestCode(spinner.getSelectedItemPosition(), checkBoxEncrypt.isChecked());
startExportVault(requestCode, cb -> {
try (OutputStream stream = new FileOutputStream(file)) {
cb.exportVault(stream);
} catch (IOException | VaultManagerException e) {
e.printStackTrace();
Dialogs.showErrorDialog(getContext(), R.string.exporting_vault_error, e);
return;
}
Uri uri = FileProvider.getUriForFile(getContext(), BuildConfig.FILE_PROVIDER_AUTHORITY, file);
Intent intent = new Intent(Intent.ACTION_SEND)
.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.setType(getExportMimeType(requestCode))
.putExtra(Intent.EXTRA_STREAM, uri);
Intent chooser = Intent.createChooser(intent, getString(R.string.pref_export_summary));
startActivity(chooser);
});
});
});
Dialogs.showSecureDialog(dialog);
}
private void onSlotManagerResult(int resultCode, Intent data) {
@ -748,21 +842,81 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
_result.putExtra("needsRecreate", true);
}
private void onExportResult(int resultCode, Intent data, boolean encrypt) {
private static int getExportRequestCode(int spinnerPos, boolean encrypt) {
if (spinnerPos == 0) {
return encrypt ? CODE_EXPORT : CODE_EXPORT_PLAIN;
}
return CODE_EXPORT_GOOGLE_URI;
}
private static VaultBackupManager.FileInfo getExportFileInfo(int spinnerPos, boolean encrypt) {
if (spinnerPos == 0) {
String filename = encrypt ? VaultManager.FILENAME_PREFIX_EXPORT : VaultManager.FILENAME_PREFIX_EXPORT_PLAIN;
return new VaultBackupManager.FileInfo(filename);
}
return new VaultBackupManager.FileInfo(VaultManager.FILENAME_PREFIX_EXPORT_URI, "txt");
}
private static String getExportMimeType(int requestCode) {
return requestCode == CODE_EXPORT_GOOGLE_URI ? "text/plain" : "application/json";
}
private void startExportVault(int requestCode, StartExportCallback cb) {
switch (requestCode) {
case CODE_EXPORT:
if (_vault.isEncryptionEnabled()) {
cb.exportVault(stream -> _vault.export(stream));
} else {
Dialogs.showSetPasswordDialog(getActivity(), new Dialogs.SlotListener() {
@Override
public void onSlotResult(Slot slot, Cipher cipher) {
VaultFileCredentials creds = new VaultFileCredentials();
try {
slot.setKey(creds.getKey(), cipher);
creds.getSlots().add(slot);
} catch (SlotException e) {
onException(e);
return;
}
cb.exportVault(stream -> _vault.export(stream, creds));
}
@Override
public void onException(Exception e) {
}
});
}
break;
case CODE_EXPORT_PLAIN:
cb.exportVault((stream) -> _vault.export(stream, null));
break;
case CODE_EXPORT_GOOGLE_URI:
cb.exportVault((stream) -> _vault.exportGoogleUris(stream));
break;
}
}
private void onExportResult(int requestCode, int resultCode, Intent data) {
Uri uri = data.getData();
if (resultCode != Activity.RESULT_OK || uri == null) {
return;
}
try (OutputStream stream = getContext().getContentResolver().openOutputStream(uri, "w")) {
_vault.export(stream, encrypt);
} catch (IOException | VaultManagerException e) {
e.printStackTrace();
Dialogs.showErrorDialog(getContext(), R.string.exporting_vault_error, e);
return;
}
startExportVault(requestCode, cb -> {
try (OutputStream stream = getContext().getContentResolver().openOutputStream(uri, "w")) {
cb.exportVault(stream);
} catch (IOException | VaultManagerException e) {
e.printStackTrace();
Dialogs.showErrorDialog(getContext(), R.string.exporting_vault_error, e);
}
Toast.makeText(getActivity(), getString(R.string.exported_vault), Toast.LENGTH_SHORT).show();
Toast.makeText(getActivity(), getString(R.string.exported_vault), Toast.LENGTH_SHORT).show();
});
}
private void onSelectBackupsLocationResult(int resultCode, Intent data) {
@ -977,4 +1131,12 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
}
}
}
private interface FinishExportCallback {
void exportVault(OutputStream stream) throws IOException, VaultManagerException;
}
private interface StartExportCallback {
void exportVault(FinishExportCallback exportCb);
}
}

View file

@ -2,6 +2,7 @@ package com.beemdevelopment.aegis.util;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
@ -34,4 +35,21 @@ public class IOUtils {
outStream.write(buf, 0, read);
}
}
public static void clearDirectory(File dir, boolean deleteRoot) {
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
clearDirectory(file, true);
} else {
file.delete();
}
}
}
if (deleteRoot) {
dir.delete();
}
}
}

View file

@ -114,17 +114,27 @@ public class VaultBackupManager {
public static class FileInfo {
private String _filename;
private String _ext;
private Date _date;
public FileInfo(String filename, Date date) {
public FileInfo(String filename, String extension, Date date) {
_filename = filename;
_ext = extension;
_date = date;
}
public FileInfo(String filename, Date date) {
this(filename, "json", date);
}
public FileInfo(String filename) {
this(filename, Calendar.getInstance().getTime());
}
public FileInfo(String filename, String extension) {
this(filename, extension, Calendar.getInstance().getTime());
}
public static FileInfo parseFilename(String filename) throws ParseException {
if (filename == null) {
throw new ParseException("The filename must not be null", 0);
@ -163,6 +173,10 @@ public class VaultBackupManager {
return _filename;
}
public String getExtension() {
return _ext;
}
public Date getDate() {
return _date;
}
@ -170,7 +184,7 @@ public class VaultBackupManager {
@NonNull
@Override
public String toString() {
return String.format("%s-%s.json", _filename, _dateFormat.format(_date));
return String.format("%s-%s.%s", _filename, _dateFormat.format(_date), _ext);
}
}

View file

@ -5,6 +5,7 @@ import android.content.Context;
import androidx.core.util.AtomicFile;
import com.beemdevelopment.aegis.Preferences;
import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
import org.json.JSONObject;
@ -12,6 +13,8 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.text.Collator;
import java.util.Collection;
import java.util.TreeSet;
@ -21,6 +24,7 @@ public class VaultManager {
public static final String FILENAME = "aegis.json";
public static final String FILENAME_PREFIX_EXPORT = "aegis-export";
public static final String FILENAME_PREFIX_EXPORT_PLAIN = "aegis-export-plain";
public static final String FILENAME_PREFIX_EXPORT_URI = "aegis-export-uri";
private Vault _vault;
private VaultFileCredentials _creds;
@ -128,11 +132,23 @@ public class VaultManager {
}
}
public void export(OutputStream stream, boolean encrypt) throws VaultManagerException {
/**
* Exports the vault bt serializing it and writing it to the given OutputStream. If encryption
* is enabled, the vault will be encrypted automatically.
*/
public void export(OutputStream stream) throws VaultManagerException {
export(stream, getCredentials());
}
/**
* Exports the vault by serializing it and writing it to the given OutputStream. If creds is
* not null, it will be used to encrypt the vault first.
*/
public void export(OutputStream stream, VaultFileCredentials creds) throws VaultManagerException {
try {
VaultFile vaultFile = new VaultFile();
if (encrypt && isEncryptionEnabled()) {
vaultFile.setContent(_vault.toJson(), _creds);
if (creds != null) {
vaultFile.setContent(_vault.toJson(), creds);
} else {
vaultFile.setContent(_vault.toJson());
}
@ -144,6 +160,21 @@ public class VaultManager {
}
}
/**
* Exports the vault by serializing the list of entries to a newline-separated list of
* Google Authenticator URI's and writing it to the given OutputStream.
*/
public void exportGoogleUris(OutputStream outStream) throws VaultManagerException {
try (PrintStream stream = new PrintStream(outStream, false, StandardCharsets.UTF_8.toString())) {
for (VaultEntry entry : getEntries()) {
GoogleAuthInfo info = new GoogleAuthInfo(entry.getInfo(), entry.getName(), entry.getIssuer());
stream.println(info.getUri().toString());
}
} catch (IOException e) {
throw new VaultManagerException(e);
}
}
public void backup() throws VaultManagerException {
_backups.create(_prefs.getBackupsLocation(), _prefs.getBackupsVersionCount());
}

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="10dp"
android:paddingTop="10dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="25dp"
android:layout_marginEnd="25dp"
android:text="@string/export_help" />
<androidx.appcompat.widget.AppCompatSpinner
android:id="@+id/spinner_export_format"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:layout_marginTop="5dp" />
<CheckBox
android:id="@+id/checkbox_export_encrypt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:layout_marginTop="5dp"
android:text="@string/export_encrypted"
android:checked="true" />
<TextView
android:id="@+id/text_export_warning"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="25dp"
android:layout_marginEnd="25dp"
android:layout_marginTop="5dp"
android:text="@string/export_warning_unencrypted"
android:textColor="#FF0000"
android:visibility="gone" />
<CheckBox
android:id="@+id/checkbox_accept"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:layout_marginTop="5dp"
android:text="@string/export_warning_accept"
android:checked="false"
android:visibility="gone" />
</LinearLayout>

View file

@ -151,7 +151,6 @@
<string name="import_error_title">حدث خطأ أو أكثر أثناء الاستيراد</string>
<string name="exporting_vault_error">حدث خطأ أثناء محاولة تصدير المخزن</string>
<string name="exported_vault">تم تصدير المخزن</string>
<string name="export_warning">هذا الفعل سيصدّر المخزن خارج ذاكرة Aegis الخاصة.</string>
<string name="encryption_set_password_error">حدث خطأ أثناء محاولة تعيين كلمة مرور.</string>
<string name="encryption_enable_biometrics_error">حدث خطأ أثناء محاولة تفعيل فتح القفل البيومتري. لبعض الأجهزة تنفيذ رديئ للمصادقة البيومترية، والأرجح أن جهازك أحدها. بدلًا من ذلك، فكّر بتحويل الإعدادات إلى كلمة مرور فقط.</string>
<string name="no_cameras_available">لا توجد كاميرا متاحة</string>

View file

@ -40,7 +40,6 @@
<string name="pref_import_app_summary">Importovat tokeny z aplikace (vyžaduje root přístup)</string>
<string name="pref_export_title">Exportovat</string>
<string name="pref_export_summary">Exportovat trezor</string>
<string name="pref_export_keep_encrypted">Ponechat trezor zašifrovaný</string>
<string name="pref_password_reminder_title">Připomenutí hesla</string>
<string name="pref_password_reminder_summary">Jednou za čas zobrazit výzvu pro zadání hesla, abyste ho nezapomněli.</string>
<string name="pref_secure_screen_title">Zabezpečení obrazovky</string>
@ -153,7 +152,6 @@
<string name="import_error_title">Při importu došlo k jedné nebo více chybám</string>
<string name="exporting_vault_error">Při pokusu o export trezoru došlo k chybě</string>
<string name="exported_vault">Trezor byl exportován</string>
<string name="export_warning">Trezor bude exportován mimo soukromé úložiště aplikace Aegis.</string>
<string name="encryption_set_password_error">Při pokusu o nastavení hesla došlo k chybě.</string>
<string name="encryption_enable_biometrics_error">Došlo k chybě při povolování biometrického odemknutí. Některá zařízení mají špatnou implementaci biometrického ověření. Bohužel, je pravděpodobné, že to vaše je jedním z nich. Zvažte možnost odemykání pouze heslem.</string>
<string name="no_cameras_available">Fotoaparát není dostupný</string>

View file

@ -120,7 +120,6 @@
<string name="import_error_title">Παρουσιάστηκαν ένα ή περισσότερα σφάλματα κατά την εισαγωγή</string>
<string name="exporting_vault_error">Παρουσιάστηκε σφάλμα κατά την προσπάθεια εξαγωγής της λίστας</string>
<string name="exported_vault">Η λίστα έχει εξαχθεί</string>
<string name="export_warning">Αυτή η ενέργεια θα εξάγει τη λίστα από την ιδιωτική αποθήκη του Aegis.</string>
<string name="no_cameras_available">Δεν υπάρχουν διαθέσιμες κάμερες</string>
<string name="read_qr_error">Παρουσιάστηκε σφάλμα κατά την προσπάθεια ανάγνωσης του κώδικα QR</string>
<string name="authentication_method_raw">Ακατέργαστη</string>

View file

@ -148,7 +148,6 @@
<string name="import_error_title">Se han producido uno o más errores durante la importación</string>
<string name="exporting_vault_error">Se ha producido un error tratando de exportar la caja fuerte</string>
<string name="exported_vault">La caja fuerte ha sido exportada</string>
<string name="export_warning">Esta acción exportará la caja fuerte fuera del almacenamiento privado de Aegis.</string>
<string name="encryption_set_password_error">Se ha producido un error tratando de establecer la contraseña.</string>
<string name="encryption_enable_biometrics_error">Se ha producido un error mientras se trataba de habilitar el desbloqueo biométrico. Algunos dispositivos tienen implementaciones deficientes de autenticación biométrica y es probable que el suyo sea uno de ellos. Considere cambiar a una configuración de sólo contraseña en su lugar.</string>
<string name="no_cameras_available">No hay ninguna cámara disponible</string>

View file

@ -144,7 +144,6 @@
<string name="import_error_title">Yksi tai useampi virhettä tapahtui tuonnin aikana</string>
<string name="exporting_vault_error">Holvin viennissä tapahtui virhe</string>
<string name="exported_vault">Holvi on viety</string>
<string name="export_warning">Tämä toiminto vie holvin Aegisin yksityisestä tallennustilasta.</string>
<string name="encryption_set_password_error">Salasanan asetuksessa tapahtui virhe.</string>
<string name="encryption_enable_biometrics_error">Biometrisen todennuksen käyttöönotossa tapahtui virhe. Biometrinen todennus on toteutettu huonosti joissain laitteissa, ja on todennäköistä, että omasi on yksi niistä. Harkitse vaihtamista vain salasanan käyttämiseen.</string>
<string name="no_cameras_available">Kameroita ei ole saatavilla</string>

View file

@ -144,7 +144,6 @@
<string name="import_error_title">Une ou plusieurs erreurs sont survenues lors de l\'importation</string>
<string name="exporting_vault_error">Une erreur est survenue en essayant d\'exporter le coffre-fort</string>
<string name="exported_vault">Le coffre-fort a été exporté</string>
<string name="export_warning">Cette action va exporter le coffre-fort hors du stockage privé d\'Aegis.</string>
<string name="encryption_set_password_error">Une erreur est survenue en essayant de définir le mot de passe.</string>
<string name="encryption_enable_biometrics_error">Une erreur est survenue lors de l\'activation du déverrouillage biométrique. Certains appareils ont de mauvaises implémentations d\'authentification biométrique et il est probable que le vôtre soit l\'un d\'entre eux. Pensez plutôt à basculer vers une configuration avec uniquement des mots de passe.</string>
<string name="no_cameras_available">Aucun appareil photo disponible</string>

View file

@ -140,7 +140,6 @@
<string name="import_error_title">Egy vagy több hiba történt az importálás során</string>
<string name="exporting_vault_error">Hiba történt a széf exportálásakor</string>
<string name="exported_vault">A széf exportálva</string>
<string name="export_warning">Ez a művelet exportálni fogja a széfet az Aegis saját tárterületéről.</string>
<string name="encryption_set_password_error">Hiba történt a jelszó beállításakor: </string>
<string name="encryption_enable_biometrics_error">Hiba történt a biometrikus feloldás engedélyezésekor</string>
<string name="no_cameras_available">Nem érhető el kamera</string>

View file

@ -44,7 +44,6 @@
<string name="pref_import_app_summary">Importa token da app (richiede accesso root)</string>
<string name="pref_export_title">Esporta</string>
<string name="pref_export_summary">Esporta la cassaforte</string>
<string name="pref_export_keep_encrypted">Mantieni la cassaforte crittografata</string>
<string name="pref_password_reminder_title">Promemoria password</string>
<string name="pref_password_reminder_summary">Mostra un promemoria per inserire la password una volta ogni tanto, in modo da non dimenticarla.</string>
<string name="pref_secure_screen_title">Sicurezza schermo</string>
@ -165,7 +164,6 @@
<string name="import_error_title">Errori durante l\'importazione</string>
<string name="exporting_vault_error">Si è verificato un errore durante il tentativo di esportare la cassaforte</string>
<string name="exported_vault">La cassaforte è stata esportata</string>
<string name="export_warning">Questa azione esporterà la cassaforte dall\'archivio privato di Aegis.</string>
<string name="encryption_set_password_error">Si è verificato un errore durante il tentativo di impostare la password.</string>
<string name="encryption_enable_biometrics_error">Si è verificato un errore durante il tentativo di attivare lo sblocco biometrico. Alcuni dispositivi hanno scarse implementazioni di autenticazione biometrica ed è probabile che il vostro sia uno di questi. Considera il passaggio a una configurazione con solo password.</string>
<string name="no_cameras_available">Nessuna fotocamera disponibile</string>

View file

@ -144,7 +144,6 @@
<string name="import_error_title">Er zijn fouten opgetreden tijdens het importeren</string>
<string name="exporting_vault_error">Er is een fout opgetreden tijdens het exporteren van de kluis</string>
<string name="exported_vault">De kluis is geëxporteerd</string>
<string name="export_warning">Deze actie zal de kluis uit de privé-opslag van Aegis exporteren.</string>
<string name="encryption_set_password_error">Er is een fout opgetreden tijdens het instellen van het wachtwoord.</string>
<string name="encryption_enable_biometrics_error">Er is een fout opgetreden bij het inschakelen van biometrische verificatie. Sommige apparaten hebben een gebrekkige implementatie van biometrische authenticatie en het is waarschijnlijk dat die van u er een van is. Overweeg om over te schakelen naar een \'Alleen wachtwoordconfiguratie\'.</string>
<string name="no_cameras_available">Geen camera\'s beschikbaar</string>

View file

@ -46,7 +46,6 @@
<string name="pref_import_app_summary">Importuj tokeny z innej aplikacji (wymagane jest konto root)</string>
<string name="pref_export_title">Eksportuj</string>
<string name="pref_export_summary">Eksportuj sejf</string>
<string name="pref_export_keep_encrypted">Zachowaj sejf zaszyfrowany</string>
<string name="pref_password_reminder_title">Przypomnienie hasła</string>
<string name="pref_password_reminder_summary">Co pewien czas pokazuj przypomnienie, aby wpisać hasło. Dzięki temu go nie zapomnisz.</string>
<string name="pref_secure_screen_title">Zabezpieczenie ekranu</string>
@ -171,7 +170,6 @@
<string name="import_error_title">Wystąpił co najmniej jeden błąd podczas importowania</string>
<string name="exporting_vault_error">Wystąpił błąd podczas eksportowania sejfu</string>
<string name="exported_vault">Sejf został wyeksportowany</string>
<string name="export_warning">Ta czynność wyeksportuje sejf z prywatnej przestrzeni dyskowej aplikacji Aegis.</string>
<string name="encryption_set_password_error">Wystąpił błąd podczas ustawiania hasła.</string>
<string name="encryption_enable_biometrics_error">Wystąpił błąd podczas włączania autoryzacji biometrycznej. Niektóre urządzenia posiadają słabe wsparcie dla tej funkcji i jest prawdopodobne, że Twoje urządzenie jest jednym z nich. Rozważ zmianę konfiguracji za pomocą hasła.</string>
<string name="no_cameras_available">Aparat jest niedostępny</string>

View file

@ -150,7 +150,6 @@
<string name="import_error_title">Um ou mais erros ocorreram durante a importação</string>
<string name="exporting_vault_error">Um erro ocorreu durante a tentativa de exportar o cofre</string>
<string name="exported_vault">O cofre foi exportado</string>
<string name="export_warning">Essa ação exportará o cofre para fora do armazenamento privado do Aegis.</string>
<string name="encryption_set_password_error">Um erro ocorreu durante a tentativa de definir a senha.</string>
<string name="encryption_enable_biometrics_error">Um erro ocorreu durante a tentativa de habilitar o desbloqueio biométrico. Alguns dispositivos têm implementações pobres de autenticação biométrica e é provável que o seu é um deles. Considere mudar para uma configuração com apenas senha.</string>
<string name="no_cameras_available">Nenhuma câmera disponível</string>

View file

@ -144,7 +144,6 @@
<string name="import_error_title">Um ou mais erros ocorreram durante a importação</string>
<string name="exporting_vault_error">Ocorreu um erro ao tentar exportar o cofre</string>
<string name="exported_vault">O cofre foi exportado</string>
<string name="export_warning">Esta ação irá exportar o cofre para o armazenamento privado do Aegis.</string>
<string name="encryption_set_password_error">Ocorreu um erro ao tentar definir a senha.</string>
<string name="encryption_enable_biometrics_error">Ocorreu um erro ao tentar ativar o desbloqueio biométrico. Alguns dispositivos têm implementações deficientes da autenticação biométrica e é provável que o seu seja um deles. Considere alternar para uma configuração somente senha.</string>
<string name="no_cameras_available">Nenhuma câmera disponível</string>

View file

@ -150,7 +150,6 @@
<string name="import_error_title">При импорте произошла одна или несколько ошибок</string>
<string name="exporting_vault_error">Произошла ошибка при попытке экспортировать хранилище</string>
<string name="exported_vault">Хранилище экспортировано</string>
<string name="export_warning">Это действие экспортирует базу данных из приватного хранилища Aegis.</string>
<string name="encryption_set_password_error">Произошла ошибка при попытке установить пароль.</string>
<string name="encryption_enable_biometrics_error">Не удалось включить биометрическую разблокировку. На некоторых устройствах плохо реализована эта функция, и, возможно, это ваш случай. Попробуйте вместо этого использовать пароль.</string>
<string name="no_cameras_available">Нет доступных камер</string>

View file

@ -147,7 +147,6 @@
<string name="import_error_title">İçe aktarmaya çalışırken bir veya birden fazla hata meydana geldi</string>
<string name="exporting_vault_error">Kasayı dışarıya aktarmaya çalışırken hata meydana geldi</string>
<string name="exported_vault">Kasa dışarıya aktarıldı</string>
<string name="export_warning">Bu hareket kasayı Aegis\'in özel depolama alanından dışarıya aktaracak!</string>
<string name="encryption_set_password_error">Parola atamayı denerken bir hata meydana geldi.</string>
<string name="encryption_enable_biometrics_error">Biyometrik kilit açmayı aktifleştirirken bir hata meydana geldi. Bazı cihazlarda biyometrik kimlik doğrulama kötü uygulanmış olabilir ve görünüşe göre cihazınız bu cihazlardan biri. Bunun yerine sadece parola ile doğrulama ayarına geçiş yapın.</string>
<string name="no_cameras_available">Hiçbir kamera kullanılabilir değil</string>

View file

@ -43,7 +43,6 @@
<string name="pref_import_app_summary">从其他应用导入令牌(需要 root 权限)</string>
<string name="pref_export_title">导出</string>
<string name="pref_export_summary">导出数据库</string>
<string name="pref_export_keep_encrypted">保持数据库加密</string>
<string name="pref_password_reminder_title">密码提醒</string>
<string name="pref_password_reminder_summary">每隔一段时间提醒您输入一次密码,以免忘记密码。</string>
<string name="pref_secure_screen_title">屏幕安全</string>
@ -162,7 +161,6 @@
<string name="import_error_title">导入时发生一个或多个错误</string>
<string name="exporting_vault_error">试图导出数据库时出错</string>
<string name="exported_vault">数据库已导出</string>
<string name="export_warning">此操作将把数据库从 Aegis 私有存储空间中导出。</string>
<string name="encryption_set_password_error">试图设置密码时发生错误。</string>
<string name="encryption_enable_biometrics_error">尝试启用生物识别解锁时发生错误。某些设备的生物识别认证实现不佳,您的很可能就是其中之一。考虑改用仅密码配置。</string>
<string name="no_cameras_available">无可用的摄像头</string>

View file

@ -89,4 +89,9 @@
<item>@string/pref_auto_lock_type_minimize</item>
<item>@string/pref_auto_lock_type_device_lock</item>
</string-array>
<string-array name="export_formats">
<item>@string/export_format_aegis</item>
<item>@string/export_format_google_auth_uri</item>
</string-array>
</resources>

View file

@ -48,7 +48,6 @@
<string name="pref_import_app_summary">Import tokens from an app (requires root access)</string>
<string name="pref_export_title">Export</string>
<string name="pref_export_summary">Export the vault</string>
<string name="pref_export_keep_encrypted">Keep the vault encrypted</string>
<string name="pref_password_reminder_title">Password reminder</string>
<string name="pref_password_reminder_summary">Show a reminder to enter the password every once in a while, so that you don\'t forget it.</string>
<string name="pref_secure_screen_title">Screen security</string>
@ -70,6 +69,13 @@
<string name="pref_set_password_title">Change password</string>
<string name="pref_set_password_summary">Set a new password which you will need to unlock your vault</string>
<string name="export_encrypted">Encrypt the vault</string>
<string name="export_help">This action will export the vault out of Aegis\' internal storage. Select the format you\'d like your export to be in:</string>
<string name="export_warning_unencrypted">You are about to export an unencrypted copy of your Aegis vault. <b>This is not recommended</b>.</string>
<string name="export_warning_accept">I understand the risk</string>
<string name="export_format_aegis">Aegis (.JSON)</string>
<string name="export_format_google_auth_uri">Text file (.TXT)</string>
<string name="choose_authentication_method">Security</string>
<string name="authentication_method_explanation">Aegis is a security-focused 2FA app. Tokens are stored in a vault, that can optionally be encrypted with a password of your choosing. If an attacker obtains your encrypted vault file, they will not be able to access the contents without knowing the password.\n\nWe\'ve preselected the option that we think would fit best for your device.</string>
<string name="authentication_method_none">None</string>
@ -96,6 +102,7 @@
<string name="password_reminder">Please enter your password. We occasionally ask you to do this so that don\'t forget it.</string>
<string name="enter_password_authy_message">It looks like your Authy tokens are encrypted. Please close Aegis, open Authy and unlock the tokens with your password. Instead, Aegis can also attempt to decrypt your Authy tokens for you, if you enter your password below.</string>
<string name="share">Share</string>
<string name="yes">Yes</string>
<string name="no">No</string>
<string name="unlock">Unlock</string>
@ -180,7 +187,6 @@
<string name="import_error_title">One or more errors occurred during the import</string>
<string name="exporting_vault_error">An error occurred while trying to export the vault</string>
<string name="exported_vault">The vault has been exported</string>
<string name="export_warning">This action will export the vault out of Aegis\' private storage.</string>
<string name="encryption_set_password_error">An error occurred while trying to set the password.</string>
<string name="encryption_enable_biometrics_error">An error occurred while trying to enable biometric unlock. Some devices have poor implementations of biometric authentication and it is likely that yours is one of them. Consider switching to a password-only configuration instead.</string>
<string name="no_cameras_available">No cameras available</string>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path name="export" path="export/" />
</paths>