Add basic support for exporting the database

pull/41/head
Alexander Bakker 7 years ago
parent 71eb487f85
commit 95638b359b

@ -5,6 +5,7 @@
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"

@ -171,7 +171,7 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback {
_databaseFile.setContent(result.Data);
_databaseFile.setCryptParameters(result.Parameters);
}
_databaseFile.save(getApplicationContext(), DatabaseManager.FILENAME);
DatabaseManager.save(getApplicationContext(), _databaseFile);
} catch (Exception e) {
setException(e);
return;

@ -3,11 +3,13 @@ package me.impy.aegis;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.drawable.Icon;
import android.media.MediaScannerConnection;
import android.os.Build;
import android.preference.PreferenceManager;
import android.support.design.widget.BottomSheetDialog;
@ -46,6 +48,7 @@ public class MainActivity extends AppCompatActivity {
private static final int CODE_DO_INTRO = 2;
private static final int CODE_DECRYPT = 3;
private static final int CODE_IMPORT = 4;
private static final int CODE_PREFERENCES = 5;
private KeyProfileAdapter _keyProfileAdapter;
private ArrayList<KeyProfile> _keyProfiles;
@ -140,6 +143,54 @@ public class MainActivity extends AppCompatActivity {
case CODE_IMPORT:
onImportResult(resultCode, data);
break;
case CODE_PREFERENCES:
onPreferencesResult(resultCode, data);
break;
}
}
private void onPreferencesResult(int resultCode, Intent data) {
if (resultCode != RESULT_OK) {
return;
}
// TODO: create a custom layout to show a message AND a checkbox
int action = data.getIntExtra("action", -1);
switch (action) {
case PreferencesActivity.ACTION_EXPORT:
final boolean[] checked = {true};
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this)
.setTitle("Export the database")
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
String filename;
try {
filename = _db.export(checked[0]);
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "An error occurred while trying to export the database", Toast.LENGTH_SHORT).show();
return;
}
// make sure the new file is visible
MediaScannerConnection.scanFile(this, new String[]{filename}, null, null);
Toast.makeText(this, "The database has been exported to: " + filename, Toast.LENGTH_SHORT).show();
})
.setNegativeButton(android.R.string.cancel, null);
if (_db.getFile().isEncrypted()) {
final String[] items = {"Keep the database encrypted"};
final boolean[] checkedItems = {true};
builder.setMultiChoiceItems(items, checkedItems, new DialogInterface.OnMultiChoiceClickListener() {
@Override
public void onClick(DialogInterface dialog, int index, boolean isChecked) {
checked[0] = isChecked;
}
});
} else {
builder.setMessage("This action will export the database out of Android's private storage.");
}
builder.show();
break;
}
}
@ -371,7 +422,7 @@ public class MainActivity extends AppCompatActivity {
switch (item.getItemId()) {
case R.id.action_settings:
Intent preferencesActivity = new Intent(this, PreferencesActivity.class);
startActivity(preferencesActivity);
startActivityForResult(preferencesActivity, CODE_PREFERENCES);
return true;
case R.id.action_import:
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);

@ -1,5 +1,7 @@
package me.impy.aegis;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.Preference;
@ -9,13 +11,14 @@ import android.support.v7.app.AppCompatActivity;
import android.widget.Toast;
public class PreferencesActivity extends AppCompatActivity {
public static final int ACTION_EXPORT = 0;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedPreferences mySharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
if (mySharedPreferences.getBoolean("pref_night_mode", false)) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
if (preferences.getBoolean("pref_night_mode", false)) {
setTheme(R.style.AppTheme_Dark);
} else {
setTheme(R.style.AppTheme_Default);
@ -29,7 +32,6 @@ public class PreferencesActivity extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
final Preference nightModePreference = findPreference("pref_night_mode");
@ -40,6 +42,20 @@ public class PreferencesActivity extends AppCompatActivity {
return true;
}
});
Preference exportPreference = findPreference("pref_export");
exportPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
Intent intent = new Intent();
intent.putExtra("action", ACTION_EXPORT);
Activity activity = getActivity();
activity.setResult(RESULT_OK, intent);
activity.finish();
return true;
}
});
}
}
}

@ -5,6 +5,7 @@ import android.content.Context;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
@ -116,37 +117,6 @@ public class DatabaseFile {
return !_slots.isEmpty() && _cryptParameters != null;
}
public void save(Context context, String filename) throws IOException {
byte[] data = serialize();
FileOutputStream file = context.openFileOutput(filename, Context.MODE_PRIVATE);
file.write(data);
file.close();
}
public static DatabaseFile load(Context context, String filename) throws Exception {
byte[] bytes;
FileInputStream file = null;
try {
file = context.openFileInput(filename);
DataInputStream stream = new DataInputStream(file);
bytes = new byte[(int) file.getChannel().size()];
stream.readFully(bytes);
stream.close();
} finally {
// always close the file
// there is no need to close the DataInputStream
if (file != null) {
file.close();
}
}
DatabaseFile db = new DatabaseFile();
db.deserialize(bytes);
return db;
}
private static void writeSection(DataOutputStream stream, byte id, byte[] data) throws IOException {
stream.write(id);

@ -1,16 +1,23 @@
package me.impy.aegis.db;
import android.content.Context;
import android.os.Environment;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import me.impy.aegis.KeyProfile;
import me.impy.aegis.crypto.CryptParameters;
import me.impy.aegis.crypto.CryptResult;
import me.impy.aegis.crypto.MasterKey;
public class DatabaseManager {
public static final String FILENAME = "aegis.db";
private static final String FILENAME = "aegis.db";
private static final String FILENAME_EXPORT = "aegis_export.db";
private static final String FILENAME_EXPORT_PLAIN = "aegis_export.json";
private MasterKey _key;
private DatabaseFile _file;
@ -22,11 +29,30 @@ public class DatabaseManager {
}
public void load() throws Exception {
_file = DatabaseFile.load(_context, FILENAME);
byte[] fileBytes;
FileInputStream file = null;
try {
file = _context.openFileInput(FILENAME);
fileBytes = new byte[(int) file.getChannel().size()];
DataInputStream stream = new DataInputStream(file);
stream.readFully(fileBytes);
stream.close();
} finally {
// always close the file stream
// there is no need to close the DataInputStream
if (file != null) {
file.close();
}
}
_file = new DatabaseFile();
_file.deserialize(fileBytes);
if (!_file.isEncrypted()) {
byte[] bytes = _file.getContent();
byte[] contentBytes = _file.getContent();
_db = new Database();
_db.deserialize(bytes);
_db.deserialize(contentBytes);
}
}
@ -40,17 +66,64 @@ public class DatabaseManager {
_key = key;
}
public static void save(Context context, DatabaseFile file) throws IOException {
byte[] bytes = file.serialize();
FileOutputStream stream = null;
try {
stream = context.openFileOutput(FILENAME, Context.MODE_PRIVATE);
stream.write(bytes);
} finally {
// always close the file stream
if (stream != null) {
stream.close();
}
}
}
public void save() throws Exception {
assertDecrypted();
byte[] bytes = _db.serialize();
byte[] dbBytes = _db.serialize();
if (!_file.isEncrypted()) {
_file.setContent(bytes);
_file.setContent(dbBytes);
} else {
CryptResult result = _key.encrypt(dbBytes);
_file.setContent(result.Data);
_file.setCryptParameters(result.Parameters);
}
save(_context, _file);
}
public String export(boolean encrypt) throws Exception {
assertDecrypted();
byte[] bytes = _db.serialize();
encrypt = encrypt && getFile().isEncrypted();
if (encrypt) {
CryptResult result = _key.encrypt(bytes);
_file.setContent(result.Data);
_file.setCryptParameters(result.Parameters);
bytes = _file.serialize();
}
File file;
FileOutputStream stream = null;
try {
File dir = new File(Environment.getExternalStorageDirectory(), "Aegis");
if (!dir.exists() && !dir.mkdirs()) {
throw new IOException("error creating external storage directory");
}
file = new File(dir.getAbsolutePath(), encrypt ? FILENAME_EXPORT : FILENAME_EXPORT_PLAIN);
stream = new FileOutputStream(file);
stream.write(bytes);
} finally {
// always close the file stream
if (stream != null) {
stream.close();
}
}
_file.save(_context, FILENAME);
return file.getAbsolutePath();
}
public void addKey(DatabaseEntry entry) throws Exception {

@ -13,4 +13,10 @@
android:key="pref_issuer"
android:title="@string/pref_issuers"
android:summary="@string/pref_issuers_description"/>
</PreferenceScreen>
<Preference
android:key="pref_export"
android:title="Export"
android:summary="Export the database"/>
</PreferenceScreen>

Loading…
Cancel
Save