Rudimentary backup feature

This commit is contained in:
Dominik Schürmann
2015-07-08 13:25:07 +02:00
parent 7b35f9b07a
commit d4fbaf9397
12 changed files with 288 additions and 96 deletions

View File

@@ -21,7 +21,7 @@ import android.os.Build;
import android.os.Handler;
/**
* Bug on Android >= 4.2
* Bug on Android >= 4.2. Fixed in 4.2.2 ?
*
* http://code.google.com/p/android/issues/detail?id=41901
*

View File

@@ -0,0 +1,137 @@
/*
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.util.ExportHelper;
import java.util.ArrayList;
public class BackupFragment extends Fragment {
// This ids for multiple key export.
private ArrayList<Long> mIdsForRepeatAskPassphrase;
private ArrayList<Long> mIdsForExport;
// This index for remembering the number of master key.
private int mIndex;
static final int REQUEST_REPEAT_PASSPHRASE = 1;
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.backup_fragment, container, false);
View mBackupAll = view.findViewById(R.id.backup_all);
View mBackupPublicKeys = view.findViewById(R.id.backup_public_keys);
mBackupAll.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
exportToFile(true);
}
});
mBackupPublicKeys.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
exportToFile(false);
}
});
return view;
}
private void exportToFile(boolean includeSecretKeys) {
if (includeSecretKeys) {
mIdsForRepeatAskPassphrase = new ArrayList<>();
Cursor cursor = getActivity().getContentResolver().query(
KeychainContract.KeyRingData.buildSecretKeyRingUri(), null, null, null, null);
try {
if (cursor != null) {
int keyIdColumn = cursor.getColumnIndex(KeychainContract.KeyRingData.MASTER_KEY_ID);
while (cursor.moveToNext()) {
long keyId = cursor.getLong(keyIdColumn);
try {
if (PassphraseCacheService.getCachedPassphrase(
getActivity(), keyId, keyId) == null) {
mIdsForRepeatAskPassphrase.add(keyId);
}
} catch (PassphraseCacheService.KeyNotFoundException e) {
// This happens when the master key is stripped
// and ignore this key.
}
}
}
} finally {
if (cursor != null) {
cursor.close();
}
}
mIndex = 0;
if (mIdsForRepeatAskPassphrase.size() != 0) {
startPassphraseActivity();
return;
}
ExportHelper exportHelper = new ExportHelper(getActivity());
exportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true);
} else {
ExportHelper exportHelper = new ExportHelper(getActivity());
exportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, false);
}
}
private void startPassphraseActivity() {
Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class);
long masterKeyId = mIdsForRepeatAskPassphrase.get(mIndex++);
intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, masterKeyId);
startActivityForResult(intent, REQUEST_REPEAT_PASSPHRASE);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_REPEAT_PASSPHRASE) {
if (resultCode != Activity.RESULT_OK) {
return;
}
if (mIndex < mIdsForRepeatAskPassphrase.size()) {
startPassphraseActivity();
return;
}
ExportHelper exportHelper = new ExportHelper(getActivity());
exportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true);
}
}
}

View File

@@ -116,12 +116,6 @@ public class KeyListFragment extends LoaderFragment
// for ConsolidateOperation
private CryptoOperationHelper<ConsolidateInputParcel, ConsolidateResult> mConsolidateOpHelper;
// This ids for multiple key export.
private ArrayList<Long> mIdsForRepeatAskPassphrase;
private ArrayList<Long> mIdsForExport;
// This index for remembering the number of master key.
private int mIndex;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -242,18 +236,6 @@ public class KeyListFragment extends LoaderFragment
showDeleteKeyDialog(mode, ids, mAdapter.isAnySecretSelected());
break;
}
case R.id.menu_key_list_multi_export: {
ids = mAdapter.getCurrentSelectedMasterKeyIds();
showMultiExportDialog(ids);
break;
}
case R.id.menu_key_list_multi_select_all: {
// select all
for (int i = 0; i < mAdapter.getCount(); i++) {
mStickyList.setItemChecked(i, true);
}
break;
}
}
return true;
}
@@ -641,48 +623,6 @@ public class KeyListFragment extends LoaderFragment
mConsolidateOpHelper.cryptoOperation();
}
private void showMultiExportDialog(long[] masterKeyIds) {
mIdsForRepeatAskPassphrase = new ArrayList<>();
mIdsForExport = new ArrayList<>();
for (long id : masterKeyIds) {
try {
if (PassphraseCacheService.getCachedPassphrase(
getActivity(), id, id) == null) {
mIdsForRepeatAskPassphrase.add(id);
}
} catch (PassphraseCacheService.KeyNotFoundException e) {
// This happens when the master key is stripped
// and ignore this key.
mIdsForExport.add(id);
}
}
mIndex = 0;
if (mIdsForRepeatAskPassphrase.size() != 0) {
startPassphraseActivity();
return;
}
mIdsForExport.addAll(mIdsForRepeatAskPassphrase);
finishExport();
}
private void startPassphraseActivity() {
Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class);
long masterKeyId = mIdsForRepeatAskPassphrase.get(mIndex++);
intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, masterKeyId);
startActivityForResult(intent, REQUEST_REPEAT_PASSPHRASE);
}
private void finishExport() {
long[] idsForMultiExport = new long[mIdsForExport.size()];
for (int i = 0; i < mIdsForExport.size(); i++) {
idsForMultiExport[i] = mIdsForExport.get(i);
}
mExportHelper.showExportKeysDialog(idsForMultiExport,
Constants.Path.APP_DIR_FILE,
mAdapter.isAnySecretSelected());
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (mImportOpHelper != null) {
@@ -693,19 +633,6 @@ public class KeyListFragment extends LoaderFragment
mConsolidateOpHelper.handleActivityResult(requestCode, resultCode, data);
}
if (requestCode == REQUEST_REPEAT_PASSPHRASE) {
if (resultCode != Activity.RESULT_OK) {
return;
}
if (mIndex < mIdsForRepeatAskPassphrase.size()) {
startPassphraseActivity();
return;
}
mIdsForExport.addAll(mIdsForRepeatAskPassphrase);
finishExport();
}
if (requestCode == REQUEST_ACTION) {
// if a result has been returned, display a notify
if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) {

View File

@@ -48,8 +48,9 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
static final int ID_KEYS = 1;
static final int ID_ENCRYPT_DECRYPT = 2;
static final int ID_APPS = 3;
static final int ID_SETTINGS = 4;
static final int ID_HELP = 5;
static final int ID_BACKUP = 4;
static final int ID_SETTINGS = 5;
static final int ID_HELP = 6;
// both of these are used for instrumentation testing only
public static final String EXTRA_SKIP_FIRST_TIME = "skip_first_time";
@@ -77,7 +78,9 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
new PrimaryDrawerItem().withName(R.string.nav_encrypt_decrypt).withIcon(FontAwesome.Icon.faw_lock)
.withIdentifier(ID_ENCRYPT_DECRYPT).withCheckable(false),
new PrimaryDrawerItem().withName(R.string.title_api_registered_apps).withIcon(CommunityMaterial.Icon.cmd_apps)
.withIdentifier(ID_APPS).withCheckable(false)
.withIdentifier(ID_APPS).withCheckable(false),
new PrimaryDrawerItem().withName(R.string.nav_backup).withIcon(CommunityMaterial.Icon.cmd_backup_restore)
.withIdentifier(ID_BACKUP).withCheckable(false)
)
.addStickyDrawerItems(
// display and stick on bottom of drawer
@@ -99,6 +102,9 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
case ID_APPS:
onAppsSelected();
break;
case ID_BACKUP:
onBackupSelected();
break;
case ID_SETTINGS:
intent = new Intent(MainActivity.this, SettingsActivity.class);
break;
@@ -192,6 +198,13 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
setFragment(frag, true);
}
private void onBackupSelected() {
mToolbar.setTitle(R.string.nav_backup);
mDrawerResult.setSelectionByIdentifier(ID_APPS, false);
Fragment frag = new BackupFragment();
setFragment(frag, true);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
// add the values which need to be saved from the drawer to the bundle
@@ -246,6 +259,8 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
mDrawerResult.setSelection(mDrawerResult.getPositionFromIdentifier(ID_ENCRYPT_DECRYPT), false);
} else if (frag instanceof AppsListFragment) {
mDrawerResult.setSelection(mDrawerResult.getPositionFromIdentifier(ID_APPS), false);
} else if (frag instanceof BackupFragment) {
mDrawerResult.setSelection(mDrawerResult.getPositionFromIdentifier(ID_BACKUP), false);
}
}

View File

@@ -363,6 +363,8 @@ public class ViewKeyActivity extends BaseNfcActivity implements
public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem editKey = menu.findItem(R.id.menu_key_view_edit);
editKey.setVisible(mIsSecret);
MenuItem exportKey = menu.findItem(R.id.menu_key_view_export_file);
exportKey.setVisible(mIsSecret);
MenuItem certifyFingerprint = menu.findItem(R.id.menu_key_view_certify_fingerprint);
certifyFingerprint.setVisible(!mIsSecret && !mIsVerified && !mIsExpired && !mIsRevoked);

View File

@@ -22,6 +22,7 @@ import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.HashMap;
import android.app.Activity;
import android.app.ActivityOptions;
@@ -62,6 +63,7 @@ import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.ui.util.QrCodeUtils;
import org.sufficientlysecure.keychain.util.ExportHelper;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.NfcHelper;
@@ -104,6 +106,7 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
View vFingerprintShareButton = view.findViewById(R.id.view_key_action_fingerprint_share);
View vFingerprintClipboardButton = view.findViewById(R.id.view_key_action_fingerprint_clipboard);
View vKeyShareButton = view.findViewById(R.id.view_key_action_key_share);
View vKeySafeButton = view.findViewById(R.id.view_key_action_key_export);
View vKeyNfcButton = view.findViewById(R.id.view_key_action_key_nfc);
View vKeyClipboardButton = view.findViewById(R.id.view_key_action_key_clipboard);
ImageButton vKeySafeSlingerButton = (ImageButton) view.findViewById(R.id.view_key_action_key_safeslinger);
@@ -129,6 +132,12 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
share(false, false);
}
});
vKeySafeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
exportToFile(mDataUri, new ProviderHelper(getActivity()));
}
});
vKeyClipboardButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -164,6 +173,25 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
return root;
}
private void exportToFile(Uri dataUri, ProviderHelper providerHelper) {
try {
Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri);
HashMap<String, Object> data = providerHelper.getGenericData(
baseUri,
new String[]{KeychainContract.Keys.MASTER_KEY_ID, KeychainContract.KeyRings.HAS_SECRET},
new int[]{ProviderHelper.FIELD_TYPE_INTEGER, ProviderHelper.FIELD_TYPE_INTEGER});
new ExportHelper(getActivity()).showExportKeysDialog(
new long[]{(Long) data.get(KeychainContract.KeyRings.MASTER_KEY_ID)},
Constants.Path.APP_DIR_FILE, ((Long) data.get(KeychainContract.KeyRings.HAS_SECRET) != 0)
);
} catch (ProviderHelper.NotFoundException e) {
Notify.create(getActivity(), R.string.error_key_not_found, Notify.Style.ERROR).show();
Log.e(Constants.TAG, "Key not found", e);
}
}
private void startSafeSlinger(Uri dataUri) {
long keyId = 0;
try {

View File

@@ -136,6 +136,7 @@ public class FileDialogFragment extends DialogFragment {
mCheckBox.setEnabled(true);
mCheckBox.setVisibility(View.VISIBLE);
mCheckBox.setText(checkboxText);
mCheckBox.setChecked(true);
}
alert.setView(view);

View File

@@ -49,7 +49,7 @@ public class ExportHelper
final boolean showSecretCheckbox) {
mExportFile = exportFile;
String title = null;
String title;
if (masterKeyIds == null) {
// export all keys
title = mActivity.getString(R.string.title_export_keys);
@@ -68,7 +68,7 @@ public class ExportHelper
mExportFile = file;
exportKeys(masterKeyIds, checked);
}
}, mActivity.getSupportFragmentManager() ,title, message, exportFile, checkMsg);
}, mActivity.getSupportFragmentManager(), title, message, exportFile, checkMsg);
}
// TODO: If ExportHelper requires pending data (see CryptoOPerationHelper), activities using