From d4fbaf9397da725489c6b86341d9eb9db6c14bdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Wed, 8 Jul 2015 13:25:07 +0200 Subject: Rudimentary backup feature --- .../compatibility/DialogFragmentWorkaround.java | 2 +- .../keychain/ui/BackupFragment.java | 137 +++++++++++++++++++++ .../keychain/ui/KeyListFragment.java | 73 ----------- .../keychain/ui/MainActivity.java | 21 +++- .../keychain/ui/ViewKeyActivity.java | 2 + .../keychain/ui/ViewKeyAdvShareFragment.java | 28 +++++ .../keychain/ui/dialog/FileDialogFragment.java | 1 + .../keychain/util/ExportHelper.java | 4 +- .../src/main/res/layout/backup_fragment.xml | 71 +++++++++++ .../res/layout/view_key_adv_share_fragment.xml | 17 +++ OpenKeychain/src/main/res/menu/key_list_multi.xml | 20 +-- OpenKeychain/src/main/res/values/strings.xml | 8 +- 12 files changed, 288 insertions(+), 96 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupFragment.java create mode 100644 OpenKeychain/src/main/res/layout/backup_fragment.xml (limited to 'OpenKeychain/src') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/DialogFragmentWorkaround.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/DialogFragmentWorkaround.java index 07799f466..e1d8c0da7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/DialogFragmentWorkaround.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/DialogFragmentWorkaround.java @@ -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 * diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupFragment.java new file mode 100644 index 000000000..714623ad4 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupFragment.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2015 Dominik Schürmann + * + * 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 . + */ + +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 mIdsForRepeatAskPassphrase; + private ArrayList 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); + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java index bb19fe2b1..9bffea504 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -116,12 +116,6 @@ public class KeyListFragment extends LoaderFragment // for ConsolidateOperation private CryptoOperationHelper mConsolidateOpHelper; - // This ids for multiple key export. - private ArrayList mIdsForRepeatAskPassphrase; - private ArrayList 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)) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java index 51ed2b012..bdcc0b82f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java @@ -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); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index a50696cf3..3d6ffb39c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -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); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java index f46b30137..0613388d7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java @@ -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 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 { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java index 63b6d26ac..5ef8618ce 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java @@ -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); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java index 9567fc9c0..cc51ef700 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java @@ -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 diff --git a/OpenKeychain/src/main/res/layout/backup_fragment.xml b/OpenKeychain/src/main/res/layout/backup_fragment.xml new file mode 100644 index 000000000..96fba954b --- /dev/null +++ b/OpenKeychain/src/main/res/layout/backup_fragment.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/view_key_adv_share_fragment.xml b/OpenKeychain/src/main/res/layout/view_key_adv_share_fragment.xml index c08d66cc1..d82506e74 100644 --- a/OpenKeychain/src/main/res/layout/view_key_adv_share_fragment.xml +++ b/OpenKeychain/src/main/res/layout/view_key_adv_share_fragment.xml @@ -125,6 +125,23 @@ android:layout_marginTop="8dp" android:background="?android:attr/listDivider" /> + + + + - + - - - - + android:title="@string/menu_delete_key" + tools:ignore="AppCompatResource" /> diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 9135c24c5..86fcdbf7e 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -96,7 +96,7 @@ "Settings" "Help" - "Export to file" + "Backup to file" "Delete key" "Manage my keys" "Search" @@ -736,6 +736,7 @@ "Open navigation drawer" "Close navigation drawer" "My Keys" + "Backup" "Type text" @@ -1316,6 +1317,11 @@ "Would you like to use this blank YubiKey NEO with OpenKeychain?\n\nPlease take away the YubiKey now, you will be prompted when it is needed again!" "Use this YubiKey" + "Backups that include your own keys must never be shared with other people!" + "All keys + your own keys" + "All keys" + "Backup" + "Certifier" "Certificate Details" -- cgit v1.2.3