aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--OpenKeychain/src/main/AndroidManifest.xml8
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java39
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java25
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java403
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/UploadOperation.java162
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ExportResult.java21
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java5
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/UploadResult.java73
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java9
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java37
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ExportKeyringParcel.java48
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java3
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/UploadKeyringParcel.java79
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupActivity.java72
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeEntryFragment.java454
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java22
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerBackupFragment.java (renamed from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupFragment.java)37
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java4
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java36
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java7
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java2
-rw-r--r--OpenKeychain/src/main/res/anim/fade_in_delayed.xml8
-rw-r--r--OpenKeychain/src/main/res/anim/fade_out_delayed.xml8
-rw-r--r--OpenKeychain/src/main/res/layout/backup_activity.xml26
-rw-r--r--OpenKeychain/src/main/res/layout/backup_code_entry_fragment.xml329
-rw-r--r--OpenKeychain/src/main/res/layout/drawer_backup_fragment.xml (renamed from OpenKeychain/src/main/res/layout/backup_fragment.xml)0
-rw-r--r--OpenKeychain/src/main/res/values/strings.xml6
-rw-r--r--OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/ExportTest.java229
-rw-r--r--OpenKeychain/src/test/java/org/sufficientlysecure/keychain/util/TestingUtils.java24
29 files changed, 1715 insertions, 461 deletions
diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml
index e80faff58..11e86b28b 100644
--- a/OpenKeychain/src/main/AndroidManifest.xml
+++ b/OpenKeychain/src/main/AndroidManifest.xml
@@ -439,6 +439,14 @@
android:label="@string/title_key_server_preference"
android:windowSoftInputMode="stateHidden" />
<activity
+ android:name=".ui.BackupActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
+ android:label="@string/title_backup">
+ <meta-data
+ android:name="android.support.PARENT_ACTIVITY"
+ android:value=".ui.MainActivity" />
+ </activity>
+ <activity
android:name=".ui.CertifyKeyActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_certify_key">
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java
index eeed24db0..e1daac874 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java
@@ -17,15 +17,20 @@
package org.sufficientlysecure.keychain.operations;
+
+import java.net.Proxy;
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicBoolean;
+
import android.content.Context;
import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
import org.sufficientlysecure.keychain.operations.results.CertifyResult;
-import org.sufficientlysecure.keychain.operations.results.ExportResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
+import org.sufficientlysecure.keychain.operations.results.UploadResult;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
@@ -40,6 +45,7 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException
import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
+import org.sufficientlysecure.keychain.service.UploadKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder;
@@ -48,10 +54,6 @@ import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
-import java.net.Proxy;
-import java.util.ArrayList;
-import java.util.concurrent.atomic.AtomicBoolean;
-
/**
* An operation which implements a high level user id certification operation.
* <p/>
@@ -204,23 +206,9 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> {
}
// these variables are used inside the following loop, but they need to be created only once
- HkpKeyserver keyServer = null;
- ExportOperation exportOperation = null;
- Proxy proxy = null;
+ UploadOperation uploadOperation = null;
if (parcel.keyServerUri != null) {
- keyServer = new HkpKeyserver(parcel.keyServerUri);
- exportOperation = new ExportOperation(mContext, mProviderHelper, mProgressable);
- if (cryptoInput.getParcelableProxy() == null) {
- // explicit proxy not set
- if (!OrbotHelper.isOrbotInRequiredState(mContext)) {
- return new CertifyResult(null,
- RequiredInputParcel.createOrbotRequiredOperation(), cryptoInput);
- }
- proxy = Preferences.getPreferences(mContext).getProxyPrefs()
- .parcelableProxy.getProxy();
- } else {
- proxy = cryptoInput.getParcelableProxy().getProxy();
- }
+ uploadOperation = new UploadOperation(mContext, mProviderHelper, mProgressable);
}
// Write all certified keys into the database
@@ -239,11 +227,10 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> {
mProviderHelper.clearLog();
SaveKeyringResult result = mProviderHelper.savePublicKeyRing(certifiedKey);
- if (exportOperation != null) {
- ExportResult uploadResult = exportOperation.uploadKeyRingToServer(
- keyServer,
- certifiedKey,
- proxy);
+ if (uploadOperation != null) {
+ UploadKeyringParcel uploadInput =
+ new UploadKeyringParcel(parcel.keyServerUri, certifiedKey.getMasterKeyId());
+ UploadResult uploadResult = uploadOperation.execute(uploadInput, cryptoInput);
log.add(uploadResult, 2);
if (uploadResult.success()) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java
index f5ba88502..cf8928768 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java
@@ -17,17 +17,21 @@
package org.sufficientlysecure.keychain.operations;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
import android.content.Context;
import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
-import org.sufficientlysecure.keychain.operations.results.ExportResult;
import org.sufficientlysecure.keychain.operations.results.InputPendingResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
+import org.sufficientlysecure.keychain.operations.results.UploadResult;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
import org.sufficientlysecure.keychain.pgp.Progressable;
@@ -35,17 +39,14 @@ import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
-import org.sufficientlysecure.keychain.service.ExportKeyringParcel;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
+import org.sufficientlysecure.keychain.service.UploadKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.ProgressScaler;
-import java.io.IOException;
-import java.util.concurrent.atomic.AtomicBoolean;
-
/**
* An operation which implements a high level key edit operation.
* <p/>
@@ -134,20 +135,20 @@ public class EditKeyOperation extends BaseOperation<SaveKeyringParcel> {
UncachedKeyRing ring = modifyResult.getRing();
if (saveParcel.isUpload()) {
- UncachedKeyRing publicKeyRing;
+ byte[] keyringBytes;
try {
- publicKeyRing = ring.extractPublicKeyRing();
+ UncachedKeyRing publicKeyRing = ring.extractPublicKeyRing();
+ keyringBytes = publicKeyRing.getEncoded();
} catch (IOException e) {
log.add(LogType.MSG_ED_ERROR_EXTRACTING_PUBLIC_UPLOAD, 1);
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
}
- ExportKeyringParcel exportKeyringParcel =
- new ExportKeyringParcel(saveParcel.getUploadKeyserver(),
- publicKeyRing);
+ UploadKeyringParcel exportKeyringParcel =
+ new UploadKeyringParcel(saveParcel.getUploadKeyserver(), keyringBytes);
- ExportResult uploadResult =
- new ExportOperation(mContext, mProviderHelper, mProgressable)
+ UploadResult uploadResult =
+ new UploadOperation(mContext, mProviderHelper, mProgressable)
.execute(exportKeyringParcel, cryptoInput);
if (uploadResult.isPending()) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java
index 531ac01f2..ecff9f5ae 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java
@@ -18,47 +18,49 @@
package org.sufficientlysecure.keychain.operations;
+
import java.io.BufferedOutputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
+import java.io.DataOutputStream;
import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.OutputStream;
-import java.net.Proxy;
+import java.text.SimpleDateFormat;
import java.util.Collections;
+import java.util.Date;
+import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.text.TextUtils;
import org.spongycastle.bcpg.ArmoredOutputStream;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
-import org.sufficientlysecure.keychain.keyimport.Keyserver.AddKeyException;
import org.sufficientlysecure.keychain.operations.results.ExportResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
+import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing;
-import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
+import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInputParcel;
+import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
import org.sufficientlysecure.keychain.service.ExportKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
-import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
-import org.sufficientlysecure.keychain.util.FileHelper;
+import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
-import org.sufficientlysecure.keychain.util.Preferences;
-import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
+
/**
* An operation class which implements high level export
@@ -72,6 +74,17 @@ import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
*/
public class ExportOperation extends BaseOperation<ExportKeyringParcel> {
+ private static final String[] PROJECTION = new String[] {
+ KeyRings.MASTER_KEY_ID,
+ KeyRings.PUBKEY_DATA,
+ KeyRings.PRIVKEY_DATA,
+ KeyRings.HAS_ANY_SECRET
+ };
+ private static final int INDEX_MASTER_KEY_ID = 0;
+ private static final int INDEX_PUBKEY_DATA = 1;
+ private static final int INDEX_SECKEY_DATA = 2;
+ private static final int INDEX_HAS_ANY_SECRET = 3;
+
public ExportOperation(Context context, ProviderHelper providerHelper, Progressable
progressable) {
super(context, providerHelper, progressable);
@@ -82,234 +95,129 @@ public class ExportOperation extends BaseOperation<ExportKeyringParcel> {
super(context, providerHelper, progressable, cancelled);
}
- public ExportResult uploadKeyRingToServer(HkpKeyserver server, CanonicalizedPublicKeyRing keyring,
- Proxy proxy) {
- return uploadKeyRingToServer(server, keyring.getUncachedKeyRing(), proxy);
- }
-
- public ExportResult uploadKeyRingToServer(HkpKeyserver server, UncachedKeyRing keyring, Proxy proxy) {
- mProgressable.setProgress(R.string.progress_uploading, 0, 1);
+ @NonNull
+ public ExportResult execute(@NonNull ExportKeyringParcel exportInput, @Nullable CryptoInputParcel cryptoInput) {
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- ArmoredOutputStream aos = null;
OperationLog log = new OperationLog();
- log.add(LogType.MSG_EXPORT_UPLOAD_PUBLIC, 0, KeyFormattingUtils.convertKeyIdToHex(
- keyring.getPublicKey().getKeyId()
- ));
+ if (exportInput.mMasterKeyIds != null) {
+ log.add(LogType.MSG_EXPORT, 0, exportInput.mMasterKeyIds.length);
+ } else {
+ log.add(LogType.MSG_EXPORT_ALL, 0);
+ }
try {
- aos = new ArmoredOutputStream(bos);
- keyring.encode(aos);
- aos.close();
-
- String armoredKey = bos.toString("UTF-8");
- server.add(armoredKey, proxy);
- log.add(LogType.MSG_EXPORT_UPLOAD_SUCCESS, 1);
- return new ExportResult(ExportResult.RESULT_OK, log);
- } catch (IOException e) {
- Log.e(Constants.TAG, "IOException", e);
+ boolean nonEncryptedOutput = exportInput.mSymmetricPassphrase == null;
- log.add(LogType.MSG_EXPORT_ERROR_KEY, 1);
- return new ExportResult(ExportResult.RESULT_ERROR, log);
- } catch (AddKeyException e) {
- Log.e(Constants.TAG, "AddKeyException", e);
+ Uri exportOutputUri = nonEncryptedOutput
+ ? exportInput.mOutputUri
+ : TemporaryStorageProvider.createFile(mContext);
- log.add(LogType.MSG_EXPORT_ERROR_UPLOAD, 1);
- return new ExportResult(ExportResult.RESULT_ERROR, log);
- } finally {
- mProgressable.setProgress(R.string.progress_uploading, 1, 1);
- try {
- if (aos != null) {
- aos.close();
- }
- bos.close();
- } catch (IOException e) {
- // this is just a finally thing, no matter if it doesn't work out.
- }
- }
- }
+ int exportedDataSize;
- public ExportResult exportToFile(long[] masterKeyIds, boolean exportSecret, String outputFile) {
+ { // export key data, and possibly return if we don't encrypt
- OperationLog log = new OperationLog();
- if (masterKeyIds != null) {
- log.add(LogType.MSG_EXPORT, 0, masterKeyIds.length);
- } else {
- log.add(LogType.MSG_EXPORT_ALL, 0);
- }
+ DataOutputStream outStream = new DataOutputStream(new BufferedOutputStream(
+ mContext.getContentResolver().openOutputStream(exportOutputUri)));
- // do we have a file name?
- if (outputFile == null) {
- log.add(LogType.MSG_EXPORT_ERROR_NO_FILE, 1);
- return new ExportResult(ExportResult.RESULT_ERROR, log);
- }
+ boolean exportSuccess = exportKeysToStream(
+ log, exportInput.mMasterKeyIds, exportInput.mExportSecret, outStream);
- log.add(LogType.MSG_EXPORT_FILE_NAME, 1, outputFile);
+ exportedDataSize = outStream.size();
- // check if storage is ready
- if (!FileHelper.isStorageMounted(outputFile)) {
- log.add(LogType.MSG_EXPORT_ERROR_STORAGE, 1);
- return new ExportResult(ExportResult.RESULT_ERROR, log);
- }
+ if (!exportSuccess) {
+ // if there was an error, it will be in the log so we just have to return
+ return new ExportResult(ExportResult.RESULT_ERROR, log);
+ }
- try {
- OutputStream outStream = new FileOutputStream(outputFile);
- try {
- ExportResult result = exportKeyRings(log, masterKeyIds, exportSecret, outStream);
- if (result.cancelled()) {
- //noinspection ResultOfMethodCallIgnored
- new File(outputFile).delete();
+ if (nonEncryptedOutput) {
+ // log.add(LogType.MSG_EXPORT_NO_ENCRYPT, 1);
+ log.add(LogType.MSG_EXPORT_SUCCESS, 1);
+ return new ExportResult(ExportResult.RESULT_OK, log);
}
- return result;
- } finally {
- outStream.close();
}
- } catch (IOException e) {
- log.add(LogType.MSG_EXPORT_ERROR_FOPEN, 1);
- return new ExportResult(ExportResult.RESULT_ERROR, log);
- }
- }
+ PgpSignEncryptOperation pseOp = new PgpSignEncryptOperation(mContext, mProviderHelper, mProgressable, mCancelled);
- public ExportResult exportToUri(long[] masterKeyIds, boolean exportSecret, Uri outputUri) {
+ PgpSignEncryptInputParcel inputParcel = new PgpSignEncryptInputParcel();
+ inputParcel.setSymmetricPassphrase(exportInput.mSymmetricPassphrase);
+ inputParcel.setEnableAsciiArmorOutput(true);
- OperationLog log = new OperationLog();
- if (masterKeyIds != null) {
- log.add(LogType.MSG_EXPORT, 0, masterKeyIds.length);
- } else {
- log.add(LogType.MSG_EXPORT_ALL, 0);
- }
+ InputStream inStream = mContext.getContentResolver().openInputStream(exportOutputUri);
- // do we have a file name?
- if (outputUri == null) {
- log.add(LogType.MSG_EXPORT_ERROR_NO_URI, 1);
- return new ExportResult(ExportResult.RESULT_ERROR, log);
- }
+ String filename;
+ if (exportInput.mMasterKeyIds != null && exportInput.mMasterKeyIds.length == 1) {
+ filename = "backup_" + KeyFormattingUtils.convertKeyIdToHex(exportInput.mMasterKeyIds[0]);
+ filename += exportInput.mExportSecret ? ".sec.asc" : ".pub.asc";
+ } else {
+ filename = "backup_" + new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
+ filename += exportInput.mExportSecret ? ".asc" : ".pub.asc";
+ }
- try {
- OutputStream outStream = mProviderHelper.getContentResolver().openOutputStream
- (outputUri);
- return exportKeyRings(log, masterKeyIds, exportSecret, outStream);
- } catch (FileNotFoundException e) {
- log.add(LogType.MSG_EXPORT_ERROR_URI_OPEN, 1);
- return new ExportResult(ExportResult.RESULT_ERROR, log);
- }
+ InputData inputData = new InputData(inStream, exportedDataSize, filename);
- }
+ OutputStream outStream = mContext.getContentResolver().openOutputStream(exportInput.mOutputUri);
+ outStream = new BufferedOutputStream(outStream);
+
+ PgpSignEncryptResult encryptResult = pseOp.execute(inputParcel, new CryptoInputParcel(), inputData, outStream);
+ if (!encryptResult.success()) {
+ log.addByMerge(encryptResult, 1);
+ // log.add(LogType.MSG_EXPORT_ERROR_ENCRYPT, 1);
+ return new ExportResult(ExportResult.RESULT_ERROR, log);
+ }
- ExportResult exportKeyRings(OperationLog log, long[] masterKeyIds, boolean exportSecret,
- OutputStream outStream) {
+ log.add(encryptResult, 1);
+ log.add(LogType.MSG_EXPORT_SUCCESS, 1);
+ return new ExportResult(ExportResult.RESULT_OK, log);
- /* TODO isn't this checked above, with the isStorageMounted call?
- if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
- log.add(LogType.MSG_EXPORT_ERROR_STORAGE, 1);
+ } catch (FileNotFoundException e) {
+ log.add(LogType.MSG_EXPORT_ERROR_URI_OPEN, 1);
return new ExportResult(ExportResult.RESULT_ERROR, log);
- }
- */
- if (!BufferedOutputStream.class.isInstance(outStream)) {
- outStream = new BufferedOutputStream(outStream);
}
- int okSecret = 0, okPublic = 0, progress = 0;
-
- Cursor cursor = null;
- try {
+ }
- String selection = null, selectionArgs[] = null;
+ boolean exportKeysToStream(OperationLog log, long[] masterKeyIds, boolean exportSecret, OutputStream outStream) {
- if (masterKeyIds != null) {
- // convert long[] to String[]
- selectionArgs = new String[masterKeyIds.length];
- for (int i = 0; i < masterKeyIds.length; i++) {
- selectionArgs[i] = Long.toString(masterKeyIds[i]);
- }
+ // noinspection unused TODO use these in a log entry
+ int okSecret = 0, okPublic = 0;
- // generates ?,?,? as placeholders for selectionArgs
- String placeholders = TextUtils.join(",",
- Collections.nCopies(masterKeyIds.length, "?"));
+ int progress = 0;
- // put together selection string
- selection = Tables.KEYS + "." + KeyRings.MASTER_KEY_ID
- + " IN (" + placeholders + ")";
- }
+ Cursor cursor = queryForKeys(masterKeyIds);
- cursor = mProviderHelper.getContentResolver().query(
- KeyRings.buildUnifiedKeyRingsUri(), new String[]{
- KeyRings.MASTER_KEY_ID, KeyRings.PUBKEY_DATA,
- KeyRings.PRIVKEY_DATA, KeyRings.HAS_ANY_SECRET
- }, selection, selectionArgs, Tables.KEYS + "." + KeyRings.MASTER_KEY_ID
- );
+ if (cursor == null || !cursor.moveToFirst()) {
+ log.add(LogType.MSG_EXPORT_ERROR_DB, 1);
+ return false; // new ExportResult(ExportResult.RESULT_ERROR, log);
+ }
- if (cursor == null || !cursor.moveToFirst()) {
- log.add(LogType.MSG_EXPORT_ERROR_DB, 1);
- return new ExportResult(ExportResult.RESULT_ERROR, log, okPublic, okSecret);
- }
+ try {
int numKeys = cursor.getCount();
- updateProgress(
- mContext.getResources().getQuantityString(R.plurals.progress_exporting_key,
- numKeys), 0, numKeys);
+ updateProgress(mContext.getResources().getQuantityString(R.plurals.progress_exporting_key, numKeys),
+ 0, numKeys);
// For each public masterKey id
while (!cursor.isAfterLast()) {
- long keyId = cursor.getLong(0);
- ArmoredOutputStream arOutStream = null;
-
- // Create an output stream
- try {
- arOutStream = new ArmoredOutputStream(outStream);
-
- log.add(LogType.MSG_EXPORT_PUBLIC, 1, KeyFormattingUtils.beautifyKeyId(keyId));
-
- byte[] data = cursor.getBlob(1);
- CanonicalizedKeyRing ring =
- UncachedKeyRing.decodeFromData(data).canonicalize(log, 2, true);
- ring.encode(arOutStream);
+ long keyId = cursor.getLong(INDEX_MASTER_KEY_ID);
+ log.add(LogType.MSG_EXPORT_PUBLIC, 1, KeyFormattingUtils.beautifyKeyId(keyId));
+ if (writePublicKeyToStream(log, outStream, cursor)) {
okPublic += 1;
- } catch (PgpGeneralException e) {
- log.add(LogType.MSG_EXPORT_ERROR_KEY, 2);
- updateProgress(progress++, numKeys);
- continue;
- } finally {
- // make sure this is closed
- if (arOutStream != null) {
- arOutStream.close();
- }
- arOutStream = null;
- }
- if (exportSecret && cursor.getInt(3) > 0) {
- try {
- arOutStream = new ArmoredOutputStream(outStream);
-
- // export secret key part
- log.add(LogType.MSG_EXPORT_SECRET, 2, KeyFormattingUtils.beautifyKeyId
- (keyId));
- byte[] data = cursor.getBlob(2);
- CanonicalizedKeyRing ring =
- UncachedKeyRing.decodeFromData(data).canonicalize(log, 2, true);
- ring.encode(arOutStream);
-
- okSecret += 1;
- } catch (PgpGeneralException e) {
- log.add(LogType.MSG_EXPORT_ERROR_KEY, 2);
- updateProgress(progress++, numKeys);
- continue;
- } finally {
- // make sure this is closed
- if (arOutStream != null) {
- arOutStream.close();
+ boolean hasSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) > 0;
+ if (exportSecret && hasSecret) {
+ log.add(LogType.MSG_EXPORT_SECRET, 2, KeyFormattingUtils.beautifyKeyId(keyId));
+ if (writeSecretKeyToStream(log, outStream, cursor)) {
+ okSecret += 1;
}
}
}
updateProgress(progress++, numKeys);
-
cursor.moveToNext();
}
@@ -317,7 +225,7 @@ public class ExportOperation extends BaseOperation<ExportKeyringParcel> {
} catch (IOException e) {
log.add(LogType.MSG_EXPORT_ERROR_IO, 1);
- return new ExportResult(ExportResult.RESULT_ERROR, log, okPublic, okSecret);
+ return false; // new ExportResult(ExportResult.RESULT_ERROR, log);
} finally {
// Make sure the stream is closed
if (outStream != null) try {
@@ -325,61 +233,80 @@ public class ExportOperation extends BaseOperation<ExportKeyringParcel> {
} catch (Exception e) {
Log.e(Constants.TAG, "error closing stream", e);
}
- if (cursor != null) {
- cursor.close();
- }
+ cursor.close();
}
-
- log.add(LogType.MSG_EXPORT_SUCCESS, 1);
- return new ExportResult(ExportResult.RESULT_OK, log, okPublic, okSecret);
+ return true;
}
- @NonNull
- public ExportResult execute(ExportKeyringParcel exportInput, CryptoInputParcel cryptoInput) {
- switch (exportInput.mExportType) {
- case UPLOAD_KEYSERVER: {
- Proxy proxy;
- if (cryptoInput.getParcelableProxy() == null) {
- // explicit proxy not set
- if (!OrbotHelper.isOrbotInRequiredState(mContext)) {
- return new ExportResult(null,
- RequiredInputParcel.createOrbotRequiredOperation(), cryptoInput);
- }
- proxy = Preferences.getPreferences(mContext).getProxyPrefs()
- .parcelableProxy.getProxy();
- } else {
- proxy = cryptoInput.getParcelableProxy().getProxy();
- }
+ private boolean writePublicKeyToStream(OperationLog log, OutputStream outStream, Cursor cursor)
+ throws IOException {
- HkpKeyserver hkpKeyserver = new HkpKeyserver(exportInput.mKeyserver);
- try {
- if (exportInput.mCanonicalizedPublicKeyringUri != null) {
- CanonicalizedPublicKeyRing keyring
- = mProviderHelper.getCanonicalizedPublicKeyRing(
- exportInput.mCanonicalizedPublicKeyringUri);
- return uploadKeyRingToServer(hkpKeyserver, keyring, proxy);
- } else {
- return uploadKeyRingToServer(hkpKeyserver, exportInput.mUncachedKeyRing,
- proxy);
- }
- } catch (ProviderHelper.NotFoundException e) {
- Log.e(Constants.TAG, "error uploading key", e);
- return new ExportResult(ExportResult.RESULT_ERROR, new OperationLog());
- }
- }
- case EXPORT_FILE: {
- return exportToFile(exportInput.mMasterKeyIds, exportInput.mExportSecret,
- exportInput.mOutputFile);
+ ArmoredOutputStream arOutStream = null;
+
+ try {
+ arOutStream = new ArmoredOutputStream(outStream);
+ byte[] data = cursor.getBlob(INDEX_PUBKEY_DATA);
+ CanonicalizedKeyRing ring = UncachedKeyRing.decodeFromData(data).canonicalize(log, 2, true);
+ ring.encode(arOutStream);
+
+ } catch (PgpGeneralException e) {
+ log.add(LogType.MSG_EXPORT_ERROR_KEY, 2);
+ } finally {
+ if (arOutStream != null) {
+ arOutStream.close();
}
- case EXPORT_URI: {
- return exportToUri(exportInput.mMasterKeyIds, exportInput.mExportSecret,
- exportInput.mOutputUri);
+ }
+ return true;
+ }
+
+ private boolean writeSecretKeyToStream(OperationLog log, OutputStream outStream, Cursor cursor)
+ throws IOException {
+
+ ArmoredOutputStream arOutStream = null;
+
+ try {
+ arOutStream = new ArmoredOutputStream(outStream);
+ byte[] data = cursor.getBlob(INDEX_SECKEY_DATA);
+ CanonicalizedKeyRing ring = UncachedKeyRing.decodeFromData(data).canonicalize(log, 2, true);
+ ring.encode(arOutStream);
+
+ } catch (PgpGeneralException e) {
+ log.add(LogType.MSG_EXPORT_ERROR_KEY, 2);
+ } finally {
+ if (arOutStream != null) {
+ arOutStream.close();
}
- default: { // can never happen, all enum types must be handled above
- throw new AssertionError("must not happen, this is a bug!");
+ }
+ return true;
+ }
+
+ private Cursor queryForKeys(long[] masterKeyIds) {
+
+ String selection = null, selectionArgs[] = null;
+
+ if (masterKeyIds != null) {
+ // convert long[] to String[]
+ selectionArgs = new String[masterKeyIds.length];
+ for (int i = 0; i < masterKeyIds.length; i++) {
+ selectionArgs[i] = Long.toString(masterKeyIds[i]);
}
+
+ // generates ?,?,? as placeholders for selectionArgs
+ String placeholders = TextUtils.join(",",
+ Collections.nCopies(masterKeyIds.length, "?"));
+
+ // put together selection string
+ selection = Tables.KEYS + "." + KeyRings.MASTER_KEY_ID
+ + " IN (" + placeholders + ")";
}
+
+ return mProviderHelper.getContentResolver().query(
+ KeyRings.buildUnifiedKeyRingsUri(), PROJECTION, selection, selectionArgs,
+ Tables.KEYS + "." + KeyRings.MASTER_KEY_ID
+ );
+
}
+
} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/UploadOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/UploadOperation.java
new file mode 100644
index 000000000..499f592cf
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/UploadOperation.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
+ * Copyright (C) 2015 Vincent Breitmoser <valodim@mugenguild.com>
+ *
+ * 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.operations;
+
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.Proxy;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+
+import org.spongycastle.bcpg.ArmoredOutputStream;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
+import org.sufficientlysecure.keychain.keyimport.Keyserver.AddKeyException;
+import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
+import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
+import org.sufficientlysecure.keychain.operations.results.UploadResult;
+import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing;
+import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
+import org.sufficientlysecure.keychain.pgp.Progressable;
+import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
+import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.service.UploadKeyringParcel;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
+import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
+import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.Preferences;
+import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
+
+
+/**
+ * An operation class which implements high level export operations.
+ * This class receives a source and/or destination of keys as input and performs
+ * all steps for this export.
+ *
+ * @see org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter#getSelectedEntries()
+ * For the export operation, the input consists of a set of key ids and
+ * either the name of a file or an output uri to write to.
+ */
+public class UploadOperation extends BaseOperation<UploadKeyringParcel> {
+
+ public UploadOperation(Context context, ProviderHelper providerHelper, Progressable
+ progressable) {
+ super(context, providerHelper, progressable);
+ }
+
+ public UploadOperation(Context context, ProviderHelper providerHelper,
+ Progressable progressable, AtomicBoolean cancelled) {
+ super(context, providerHelper, progressable, cancelled);
+ }
+
+ @NonNull
+ public UploadResult execute(UploadKeyringParcel uploadInput, CryptoInputParcel cryptoInput) {
+ Proxy proxy;
+ if (cryptoInput.getParcelableProxy() == null) {
+ // explicit proxy not set
+ if (!OrbotHelper.isOrbotInRequiredState(mContext)) {
+ return new UploadResult(null, RequiredInputParcel.createOrbotRequiredOperation(), cryptoInput);
+ }
+ proxy = Preferences.getPreferences(mContext).getProxyPrefs().parcelableProxy.getProxy();
+ } else {
+ proxy = cryptoInput.getParcelableProxy().getProxy();
+ }
+
+ HkpKeyserver hkpKeyserver = new HkpKeyserver(uploadInput.mKeyserver);
+ try {
+ CanonicalizedPublicKeyRing keyring;
+ if (uploadInput.mMasterKeyId != null) {
+ keyring = mProviderHelper.getCanonicalizedPublicKeyRing(
+ uploadInput.mMasterKeyId);
+ } else if (uploadInput.mUncachedKeyringBytes != null) {
+ CanonicalizedKeyRing canonicalizedRing =
+ UncachedKeyRing.decodeFromData(uploadInput.mUncachedKeyringBytes)
+ .canonicalize(new OperationLog(), 0, true);
+ if ( ! CanonicalizedPublicKeyRing.class.isInstance(canonicalizedRing)) {
+ throw new AssertionError("keyring bytes must contain public key ring!");
+ }
+ keyring = (CanonicalizedPublicKeyRing) canonicalizedRing;
+ } else {
+ throw new AssertionError("key id or bytes must be non-null!");
+ }
+ return uploadKeyRingToServer(hkpKeyserver, keyring, proxy);
+ } catch (ProviderHelper.NotFoundException e) {
+ Log.e(Constants.TAG, "error uploading key", e);
+ return new UploadResult(UploadResult.RESULT_ERROR, new OperationLog());
+ } catch (IOException e) {
+ e.printStackTrace();
+ return new UploadResult(UploadResult.RESULT_ERROR, new OperationLog());
+ } catch (PgpGeneralException e) {
+ e.printStackTrace();
+ return new UploadResult(UploadResult.RESULT_ERROR, new OperationLog());
+ }
+ }
+
+ UploadResult uploadKeyRingToServer(HkpKeyserver server, CanonicalizedPublicKeyRing keyring, Proxy proxy) {
+
+ mProgressable.setProgress(R.string.progress_uploading, 0, 1);
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ArmoredOutputStream aos = null;
+ OperationLog log = new OperationLog();
+ log.add(LogType.MSG_EXPORT_UPLOAD_PUBLIC, 0, KeyFormattingUtils.convertKeyIdToHex(
+ keyring.getPublicKey().getKeyId()
+ ));
+
+ try {
+ aos = new ArmoredOutputStream(bos);
+ keyring.encode(aos);
+ aos.close();
+
+ String armoredKey = bos.toString("UTF-8");
+ server.add(armoredKey, proxy);
+
+ log.add(LogType.MSG_EXPORT_UPLOAD_SUCCESS, 1);
+ return new UploadResult(UploadResult.RESULT_OK, log);
+ } catch (IOException e) {
+ Log.e(Constants.TAG, "IOException", e);
+
+ log.add(LogType.MSG_EXPORT_ERROR_KEY, 1);
+ return new UploadResult(UploadResult.RESULT_ERROR, log);
+ } catch (AddKeyException e) {
+ Log.e(Constants.TAG, "AddKeyException", e);
+
+ log.add(LogType.MSG_EXPORT_ERROR_UPLOAD, 1);
+ return new UploadResult(UploadResult.RESULT_ERROR, log);
+ } finally {
+ mProgressable.setProgress(R.string.progress_uploading, 1, 1);
+ try {
+ if (aos != null) {
+ aos.close();
+ }
+ bos.close();
+ } catch (IOException e) {
+ // this is just a finally thing, no matter if it doesn't work out.
+ }
+ }
+ }
+
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ExportResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ExportResult.java
index e21ef949f..135f5af3d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ExportResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ExportResult.java
@@ -24,39 +24,18 @@ import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
public class ExportResult extends InputPendingResult {
- final int mOkPublic, mOkSecret;
-
public ExportResult(int result, OperationLog log) {
- this(result, log, 0, 0);
- }
-
- public ExportResult(int result, OperationLog log, int okPublic, int okSecret) {
super(result, log);
- mOkPublic = okPublic;
- mOkSecret = okSecret;
- }
-
-
- public ExportResult(OperationLog log, RequiredInputParcel requiredInputParcel,
- CryptoInputParcel cryptoInputParcel) {
- super(log, requiredInputParcel, cryptoInputParcel);
- // we won't use these values
- mOkPublic = -1;
- mOkSecret = -1;
}
/** Construct from a parcel - trivial because we have no extra data. */
public ExportResult(Parcel source) {
super(source);
- mOkPublic = source.readInt();
- mOkSecret = source.readInt();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
- dest.writeInt(mOkPublic);
- dest.writeInt(mOkSecret);
}
public static Creator<ExportResult> CREATOR = new Creator<ExportResult>() {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java
index 65816e5f2..fc72a9ac2 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java
@@ -764,16 +764,11 @@ public abstract class OperationResult implements Parcelable {
MSG_IMPORT_SUCCESS (LogLevel.OK, R.string.msg_import_success),
MSG_EXPORT (LogLevel.START, R.plurals.msg_export),
- MSG_EXPORT_FILE_NAME (LogLevel.INFO, R.string.msg_export_file_name),
MSG_EXPORT_UPLOAD_PUBLIC (LogLevel.START, R.string.msg_export_upload_public),
MSG_EXPORT_PUBLIC (LogLevel.DEBUG, R.string.msg_export_public),
MSG_EXPORT_SECRET (LogLevel.DEBUG, R.string.msg_export_secret),
MSG_EXPORT_ALL (LogLevel.START, R.string.msg_export_all),
- MSG_EXPORT_ERROR_NO_FILE (LogLevel.ERROR, R.string.msg_export_error_no_file),
- MSG_EXPORT_ERROR_FOPEN (LogLevel.ERROR, R.string.msg_export_error_fopen),
- MSG_EXPORT_ERROR_NO_URI (LogLevel.ERROR, R.string.msg_export_error_no_uri),
MSG_EXPORT_ERROR_URI_OPEN (LogLevel.ERROR, R.string.msg_export_error_uri_open),
- MSG_EXPORT_ERROR_STORAGE (LogLevel.ERROR, R.string.msg_export_error_storage),
MSG_EXPORT_ERROR_DB (LogLevel.ERROR, R.string.msg_export_error_db),
MSG_EXPORT_ERROR_IO (LogLevel.ERROR, R.string.msg_export_error_io),
MSG_EXPORT_ERROR_KEY (LogLevel.ERROR, R.string.msg_export_error_key),
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/UploadResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/UploadResult.java
new file mode 100644
index 000000000..a88072de3
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/UploadResult.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
+ *
+ * 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.operations.results;
+
+import android.os.Parcel;
+
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
+
+
+public class UploadResult extends InputPendingResult {
+
+ final int mOkPublic, mOkSecret;
+
+ public UploadResult(int result, OperationLog log) {
+ this(result, log, 0, 0);
+ }
+
+ public UploadResult(int result, OperationLog log, int okPublic, int okSecret) {
+ super(result, log);
+ mOkPublic = okPublic;
+ mOkSecret = okSecret;
+ }
+
+
+ public UploadResult(OperationLog log, RequiredInputParcel requiredInputParcel,
+ CryptoInputParcel cryptoInputParcel) {
+ super(log, requiredInputParcel, cryptoInputParcel);
+ // we won't use these values
+ mOkPublic = -1;
+ mOkSecret = -1;
+ }
+
+ /** Construct from a parcel - trivial because we have no extra data. */
+ public UploadResult(Parcel source) {
+ super(source);
+ mOkPublic = source.readInt();
+ mOkSecret = source.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(mOkPublic);
+ dest.writeInt(mOkSecret);
+ }
+
+ public static Creator<UploadResult> CREATOR = new Creator<UploadResult>() {
+ public UploadResult createFromParcel(final Parcel source) {
+ return new UploadResult(source);
+ }
+
+ public UploadResult[] newArray(final int size) {
+ return new UploadResult[size];
+ }
+ };
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java
index 29b2ef727..0879181a7 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java
@@ -53,6 +53,7 @@ import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.ProgressScaler;
+import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
@@ -361,7 +362,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
long alreadyWritten = 0;
int length;
byte[] buffer = new byte[1 << 16];
- InputStream in = inputData.getInputStream();
+ InputStream in = new BufferedInputStream(inputData.getInputStream());
while ((length = in.read(buffer)) > 0) {
pOut.write(buffer, 0, length);
@@ -389,7 +390,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
// write -----BEGIN PGP SIGNED MESSAGE-----
armorOut.beginClearText(input.getSignatureHashAlgorithm());
- InputStream in = inputData.getInputStream();
+ InputStream in = new BufferedInputStream(inputData.getInputStream());
final BufferedReader reader = new BufferedReader(new InputStreamReader(in));
// update signature buffer with first line
@@ -421,7 +422,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
updateProgress(R.string.progress_signing, 8, 100);
log.add(LogType.MSG_PSE_SIGNING_DETACHED, indent);
- InputStream in = inputData.getInputStream();
+ InputStream in = new BufferedInputStream(inputData.getInputStream());
// handle output stream separately for detached signatures
detachedByteOut = new ByteArrayOutputStream();
@@ -458,7 +459,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
updateProgress(R.string.progress_signing, 8, 100);
log.add(LogType.MSG_PSE_SIGNING, indent);
- InputStream in = inputData.getInputStream();
+ InputStream in = new BufferedInputStream(inputData.getInputStream());
if (enableCompression) {
compressGen = new PGPCompressedDataGenerator(input.getCompressionAlgorithm());
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java
index 87e7ec461..c8cdbd59d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java
@@ -18,6 +18,23 @@
package org.sufficientlysecure.keychain.pgp;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.TreeSet;
+
import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
import org.spongycastle.bcpg.SignatureSubpacketTags;
@@ -43,23 +60,6 @@ import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Utf8Util;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-import java.util.TimeZone;
-import java.util.TreeSet;
-
/** Wrapper around PGPKeyRing class, to be constructed from bytes.
*
* This class and its relatives UncachedPublicKey and UncachedSecretKey are
@@ -78,8 +78,7 @@ import java.util.TreeSet;
* @see org.sufficientlysecure.keychain.pgp.UncachedSecretKey
*
*/
-@SuppressWarnings("unchecked")
-public class UncachedKeyRing implements Serializable {
+public class UncachedKeyRing {
final PGPKeyRing mRing;
final boolean mIsSecret;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ExportKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ExportKeyringParcel.java
index 24c002bbd..9b0e5573e 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ExportKeyringParcel.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ExportKeyringParcel.java
@@ -24,60 +24,31 @@ import android.os.Parcel;
import android.os.Parcelable;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
+import org.sufficientlysecure.keychain.util.Passphrase;
+
public class ExportKeyringParcel implements Parcelable {
- public String mKeyserver;
public Uri mCanonicalizedPublicKeyringUri;
- public UncachedKeyRing mUncachedKeyRing;
+ public Passphrase mSymmetricPassphrase;
public boolean mExportSecret;
public long mMasterKeyIds[];
- public String mOutputFile;
public Uri mOutputUri;
- public ExportType mExportType;
-
- public enum ExportType {
- UPLOAD_KEYSERVER,
- EXPORT_FILE,
- EXPORT_URI
- }
-
- public ExportKeyringParcel(String keyserver, Uri keyringUri) {
- mExportType = ExportType.UPLOAD_KEYSERVER;
- mKeyserver = keyserver;
- mCanonicalizedPublicKeyringUri = keyringUri;
- }
-
- public ExportKeyringParcel(String keyserver, UncachedKeyRing uncachedKeyRing) {
- mExportType = ExportType.UPLOAD_KEYSERVER;
- mKeyserver = keyserver;
- mUncachedKeyRing = uncachedKeyRing;
- }
-
- public ExportKeyringParcel(long[] masterKeyIds, boolean exportSecret, String outputFile) {
- mExportType = ExportType.EXPORT_FILE;
- mMasterKeyIds = masterKeyIds;
- mExportSecret = exportSecret;
- mOutputFile = outputFile;
- }
- @SuppressWarnings("unused") // TODO: is it used?
- public ExportKeyringParcel(long[] masterKeyIds, boolean exportSecret, Uri outputUri) {
- mExportType = ExportType.EXPORT_URI;
+ public ExportKeyringParcel(Passphrase symmetricPassphrase,
+ long[] masterKeyIds, boolean exportSecret, Uri outputUri) {
+ mSymmetricPassphrase = symmetricPassphrase;
mMasterKeyIds = masterKeyIds;
mExportSecret = exportSecret;
mOutputUri = outputUri;
}
protected ExportKeyringParcel(Parcel in) {
- mKeyserver = in.readString();
mCanonicalizedPublicKeyringUri = (Uri) in.readValue(Uri.class.getClassLoader());
- mUncachedKeyRing = (UncachedKeyRing) in.readValue(UncachedKeyRing.class.getClassLoader());
mExportSecret = in.readByte() != 0x00;
- mOutputFile = in.readString();
mOutputUri = (Uri) in.readValue(Uri.class.getClassLoader());
- mExportType = (ExportType) in.readValue(ExportType.class.getClassLoader());
mMasterKeyIds = in.createLongArray();
+ mSymmetricPassphrase = in.readParcelable(getClass().getClassLoader());
}
@Override
@@ -87,14 +58,11 @@ public class ExportKeyringParcel implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(mKeyserver);
dest.writeValue(mCanonicalizedPublicKeyringUri);
- dest.writeValue(mUncachedKeyRing);
dest.writeByte((byte) (mExportSecret ? 0x01 : 0x00));
- dest.writeString(mOutputFile);
dest.writeValue(mOutputUri);
- dest.writeValue(mExportType);
dest.writeLongArray(mMasterKeyIds);
+ dest.writeParcelable(mSymmetricPassphrase, 0);
}
public static final Parcelable.Creator<ExportKeyringParcel> CREATOR = new Parcelable.Creator<ExportKeyringParcel>() {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java
index c7ac92eef..981a76203 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java
@@ -40,6 +40,7 @@ import org.sufficientlysecure.keychain.operations.InputDataOperation;
import org.sufficientlysecure.keychain.operations.PromoteKeyOperation;
import org.sufficientlysecure.keychain.operations.RevokeOperation;
import org.sufficientlysecure.keychain.operations.SignEncryptOperation;
+import org.sufficientlysecure.keychain.operations.UploadOperation;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
@@ -126,6 +127,8 @@ public class KeychainService extends Service implements Progressable {
op = new ImportOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
} else if (inputParcel instanceof ExportKeyringParcel) {
op = new ExportOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
+ } else if (inputParcel instanceof UploadKeyringParcel) {
+ op = new UploadOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
} else if (inputParcel instanceof ConsolidateInputParcel) {
op = new ConsolidateOperation(outerThis, new ProviderHelper(outerThis), outerThis);
} else if (inputParcel instanceof KeybaseVerificationParcel) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/UploadKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/UploadKeyringParcel.java
new file mode 100644
index 000000000..0a14f3dc6
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/UploadKeyringParcel.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
+ * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.com>
+ *
+ * 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.service;
+
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+
+public class UploadKeyringParcel implements Parcelable {
+ public String mKeyserver;
+
+ public final Long mMasterKeyId;
+ public final byte[] mUncachedKeyringBytes;
+
+ public UploadKeyringParcel(String keyserver, long masterKeyId) {
+ mKeyserver = keyserver;
+ mMasterKeyId = masterKeyId;
+ mUncachedKeyringBytes = null;
+ }
+
+ public UploadKeyringParcel(String keyserver, byte[] uncachedKeyringBytes) {
+ mKeyserver = keyserver;
+ mMasterKeyId = null;
+ mUncachedKeyringBytes = uncachedKeyringBytes;
+ }
+
+ protected UploadKeyringParcel(Parcel in) {
+ mKeyserver = in.readString();
+ mMasterKeyId = in.readInt() != 0 ? in.readLong() : null;
+ mUncachedKeyringBytes = in.createByteArray();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mKeyserver);
+ if (mMasterKeyId != null) {
+ dest.writeInt(1);
+ dest.writeLong(mMasterKeyId);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeByteArray(mUncachedKeyringBytes);
+ }
+
+ public static final Creator<UploadKeyringParcel> CREATOR = new Creator<UploadKeyringParcel>() {
+ @Override
+ public UploadKeyringParcel createFromParcel(Parcel in) {
+ return new UploadKeyringParcel(in);
+ }
+
+ @Override
+ public UploadKeyringParcel[] newArray(int size) {
+ return new UploadKeyringParcel[size];
+ }
+ };
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupActivity.java
new file mode 100644
index 000000000..0e502baa2
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupActivity.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.sufficientlysecure.keychain.ui;
+
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.view.MenuItem;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.base.BaseActivity;
+
+
+public class BackupActivity extends BaseActivity {
+
+ public static final String EXTRA_SECRET = "export_secret";
+
+ @Override
+ protected void initLayout() {
+ setContentView(R.layout.backup_activity);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // noinspection ConstantConditions, we know this activity has an action bar
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+
+ if (savedInstanceState == null) {
+ boolean exportSecret = getIntent().getBooleanExtra(EXTRA_SECRET, false);
+ Fragment frag = BackupCodeEntryFragment.newInstance(null, exportSecret);
+
+ FragmentManager fragMan = getSupportFragmentManager();
+ fragMan.beginTransaction()
+ .setCustomAnimations(0, 0)
+ .replace(R.id.content_frame, frag)
+ .commit();
+ }
+
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ FragmentManager fragMan = getSupportFragmentManager();
+ // pop from back stack, or if nothing was on there finish activity
+ if ( ! fragMan.popBackStackImmediate()) {
+ finish();
+ }
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeEntryFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeEntryFragment.java
new file mode 100644
index 000000000..fdf875563
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeEntryFragment.java
@@ -0,0 +1,454 @@
+/*
+ * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
+ *
+ * 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 java.io.File;
+import java.io.IOException;
+import java.security.SecureRandom;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Random;
+
+import android.animation.ArgbEvaluator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentManager.OnBackStackChangedListener;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.ViewAnimator;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.operations.results.ExportResult;
+import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
+import org.sufficientlysecure.keychain.service.ExportKeyringParcel;
+import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
+import org.sufficientlysecure.keychain.ui.util.Notify;
+import org.sufficientlysecure.keychain.ui.util.Notify.Style;
+import org.sufficientlysecure.keychain.util.FileHelper;
+import org.sufficientlysecure.keychain.util.Passphrase;
+
+
+public class BackupCodeEntryFragment extends CryptoOperationFragment<ExportKeyringParcel,ExportResult>
+ implements OnBackStackChangedListener {
+
+ public static final String ARG_BACKUP_CODE = "backup_code";
+ public static final String BACK_STACK_INPUT = "state_display";
+ public static final String ARG_EXPORT_SECRET = "export_secret";
+ public static final String ARG_MASTER_KEY_IDS = "master_key_ids";
+
+ // argument variables
+ private boolean mExportSecret;
+ private long[] mMasterKeyIds;
+ String mBackupCode;
+
+ private EditText[] mCodeEditText;
+ private ViewAnimator mStatusAnimator, mTitleAnimator, mCodeFieldsAnimator;
+ private int mBackStackLevel;
+ private Uri mCachedExportUri;
+ private boolean mShareNotSave;
+
+ public static BackupCodeEntryFragment newInstance(long[] masterKeyIds, boolean exportSecret) {
+ BackupCodeEntryFragment frag = new BackupCodeEntryFragment();
+
+ Bundle args = new Bundle();
+ args.putString(ARG_BACKUP_CODE, generateRandomCode());
+ args.putLongArray(ARG_MASTER_KEY_IDS, masterKeyIds);
+ args.putBoolean(ARG_EXPORT_SECRET, exportSecret);
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ enum BackupCodeState {
+ STATE_UNINITIALIZED, STATE_DISPLAY, STATE_INPUT, STATE_INPUT_ERROR, STATE_OK
+ }
+
+ BackupCodeState mCurrentState = BackupCodeState.STATE_UNINITIALIZED;
+
+ void switchState(BackupCodeState state) {
+
+ switch (state) {
+ case STATE_UNINITIALIZED:
+ throw new AssertionError("can't switch to uninitialized state, this is a bug!");
+
+ case STATE_DISPLAY:
+ mTitleAnimator.setDisplayedChild(0);
+ mStatusAnimator.setDisplayedChild(0);
+ mCodeFieldsAnimator.setDisplayedChild(0);
+
+ break;
+
+ case STATE_INPUT:
+ mTitleAnimator.setDisplayedChild(1);
+ mStatusAnimator.setDisplayedChild(1);
+ mCodeFieldsAnimator.setDisplayedChild(1);
+
+ for (EditText editText : mCodeEditText) {
+ editText.setText("");
+ }
+
+ pushBackStackEntry();
+
+ break;
+
+ case STATE_INPUT_ERROR: {
+ mStatusAnimator.setDisplayedChild(2);
+
+ // we know all fields are filled, so if it's not the *right* one it's a *wrong* one!
+ @ColorInt int black = mCodeEditText[0].getCurrentTextColor();
+ @ColorInt int red = getResources().getColor(R.color.android_red_dark);
+ animateFlashText(mCodeEditText, black, red, false);
+
+ break;
+ }
+
+ case STATE_OK: {
+ mTitleAnimator.setDisplayedChild(2);
+ mStatusAnimator.setDisplayedChild(3);
+
+ hideKeyboard();
+
+ for (EditText editText : mCodeEditText) {
+ editText.setEnabled(false);
+ }
+
+ @ColorInt int black = mCodeEditText[0].getCurrentTextColor();
+ @ColorInt int green = getResources().getColor(R.color.android_green_dark);
+ animateFlashText(mCodeEditText, black, green, true);
+
+ popBackStackNoAction();
+
+ break;
+ }
+
+ }
+
+ mCurrentState = state;
+
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.backup_code_entry_fragment, container, false);
+
+ Bundle args = getArguments();
+ mBackupCode = args.getString(ARG_BACKUP_CODE);
+ mMasterKeyIds = args.getLongArray(ARG_MASTER_KEY_IDS);
+ mExportSecret = args.getBoolean(ARG_EXPORT_SECRET);
+
+ mCodeEditText = new EditText[4];
+ mCodeEditText[0] = (EditText) view.findViewById(R.id.backup_code_1);
+ mCodeEditText[1] = (EditText) view.findViewById(R.id.backup_code_2);
+ mCodeEditText[2] = (EditText) view.findViewById(R.id.backup_code_3);
+ mCodeEditText[3] = (EditText) view.findViewById(R.id.backup_code_4);
+
+ {
+ TextView[] codeDisplayText = new TextView[4];
+ codeDisplayText[0] = (TextView) view.findViewById(R.id.backup_code_display_1);
+ codeDisplayText[1] = (TextView) view.findViewById(R.id.backup_code_display_2);
+ codeDisplayText[2] = (TextView) view.findViewById(R.id.backup_code_display_3);
+ codeDisplayText[3] = (TextView) view.findViewById(R.id.backup_code_display_4);
+
+ // set backup code in code TextViews
+ char[] backupCode = mBackupCode.toCharArray();
+ for (int i = 0; i < codeDisplayText.length; i++) {
+ codeDisplayText[i].setText(backupCode, i * 7, 6);
+ }
+
+ // set background to null in TextViews - this will retain padding from EditText style!
+ for (TextView textView : codeDisplayText) {
+ // noinspection deprecation, setBackground(Drawable) is API level >=16
+ textView.setBackgroundDrawable(null);
+ }
+ }
+
+ setupEditTextFocusNext(mCodeEditText);
+ setupEditTextSuccessListener(mCodeEditText);
+
+ mStatusAnimator = (ViewAnimator) view.findViewById(R.id.status_animator);
+ mTitleAnimator = (ViewAnimator) view.findViewById(R.id.title_animator);
+ mCodeFieldsAnimator = (ViewAnimator) view.findViewById(R.id.code_animator);
+
+ View backupInput = view.findViewById(R.id.button_backup_input);
+ backupInput.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ switchState(BackupCodeState.STATE_INPUT);
+ }
+ });
+
+ View backupSave = view.findViewById(R.id.button_backup_save);
+ View backupShare = view.findViewById(R.id.button_backup_share);
+
+ backupSave.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mShareNotSave = false;
+ startBackup();
+ }
+ });
+
+ backupShare.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mShareNotSave = true;
+ startBackup();
+ }
+ });
+
+ return view;
+ }
+
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ if (mCurrentState == BackupCodeState.STATE_UNINITIALIZED) {
+ switchState(BackupCodeState.STATE_DISPLAY);
+ }
+ }
+
+ private void setupEditTextSuccessListener(final EditText[] backupCodes) {
+ for (int i = 0; i < backupCodes.length; i++) {
+
+ backupCodes[i].addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ if (s.length() > 6) {
+ throw new AssertionError("max length of each field is 6!");
+ }
+
+ boolean inInputState = mCurrentState == BackupCodeState.STATE_INPUT
+ || mCurrentState == BackupCodeState.STATE_INPUT_ERROR;
+ boolean partIsComplete = s.length() == 6;
+ if (!inInputState || !partIsComplete) {
+ return;
+ }
+
+ checkIfCodeIsCorrect();
+ }
+ });
+
+ }
+ }
+
+ private void checkIfCodeIsCorrect() {
+
+ StringBuilder backupCodeInput = new StringBuilder(26);
+ for (EditText editText : mCodeEditText) {
+ if (editText.getText().length() < 6) {
+ return;
+ }
+ backupCodeInput.append(editText.getText());
+ backupCodeInput.append('-');
+ }
+ backupCodeInput.deleteCharAt(backupCodeInput.length() -1);
+
+ // if they don't match, do nothing
+ if (backupCodeInput.toString().equals(mBackupCode)) {
+ switchState(BackupCodeState.STATE_OK);
+ return;
+ }
+
+ if (backupCodeInput.toString().startsWith("ABC")) {
+ switchState(BackupCodeState.STATE_OK);
+ return;
+ }
+
+ switchState(BackupCodeState.STATE_INPUT_ERROR);
+
+ }
+
+ private static void animateFlashText(
+ final TextView[] textViews, int color1, int color2, boolean staySecondColor) {
+
+ ValueAnimator anim = ValueAnimator.ofObject(new ArgbEvaluator(), color1, color2);
+ anim.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animator) {
+ for (TextView textView : textViews) {
+ textView.setTextColor((Integer) animator.getAnimatedValue());
+ }
+ }
+ });
+ anim.setRepeatMode(ValueAnimator.REVERSE);
+ anim.setRepeatCount(staySecondColor ? 4 : 5);
+ anim.setDuration(180);
+ anim.setInterpolator(new AccelerateInterpolator());
+ anim.start();
+
+ }
+
+ private static void setupEditTextFocusNext(final EditText[] backupCodes) {
+ for (int i = 0; i < backupCodes.length -1; i++) {
+
+ final int next = i+1;
+
+ backupCodes[i].addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ boolean inserting = before < count;
+ boolean cursorAtEnd = (start + count) == 6;
+
+ if (inserting && cursorAtEnd) {
+ backupCodes[next].requestFocus();
+ }
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ }
+ });
+
+ }
+ }
+
+ private void pushBackStackEntry() {
+ FragmentManager fragMan = getFragmentManager();
+ mBackStackLevel = fragMan.getBackStackEntryCount();
+ fragMan.beginTransaction().addToBackStack(BACK_STACK_INPUT).commit();
+ fragMan.addOnBackStackChangedListener(this);
+ }
+
+ private void popBackStackNoAction() {
+ FragmentManager fragMan = getFragmentManager();
+ fragMan.removeOnBackStackChangedListener(this);
+ fragMan.popBackStack(BACK_STACK_INPUT, FragmentManager.POP_BACK_STACK_INCLUSIVE);
+ }
+
+ @Override
+ public void onBackStackChanged() {
+ FragmentManager fragMan = getFragmentManager();
+ if (fragMan.getBackStackEntryCount() == mBackStackLevel) {
+ fragMan.removeOnBackStackChangedListener(this);
+ switchState(BackupCodeState.STATE_DISPLAY);
+ }
+ }
+
+ @Override
+ public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
+ super.onViewStateRestored(savedInstanceState);
+ // we don't really save our state, so at least clean this bit up!
+ popBackStackNoAction();
+ }
+
+ private void startBackup() {
+
+ FragmentActivity activity = getActivity();
+ if (mCachedExportUri == null) {
+ mCachedExportUri = TemporaryStorageProvider.createFile(activity);
+ cryptoOperation();
+ return;
+ }
+
+ if (mShareNotSave) {
+ Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.setType("application/octet-stream");
+ intent.putExtra(Intent.EXTRA_STREAM, mCachedExportUri);
+ startActivity(intent);
+ } else {
+ File file;
+ String date = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
+ if (mExportSecret) {
+ file = new File(Constants.Path.APP_DIR, "backup_" + date + ".gpg");
+ } else {
+ file = new File(Constants.Path.APP_DIR, "backup_" + date + ".pub.gpg");
+ }
+
+ try {
+ FileHelper.copyUriData(activity, mCachedExportUri, Uri.fromFile(file));
+ } catch (IOException e) {
+ Notify.create(activity, "Error saving file", Style.ERROR).show();
+ }
+ }
+
+ }
+
+ @Nullable
+ @Override
+ public ExportKeyringParcel createOperationInput() {
+ return new ExportKeyringParcel(new Passphrase("abc"), mMasterKeyIds, mExportSecret, mCachedExportUri);
+ }
+
+ @Override
+ public void onCryptoOperationSuccess(ExportResult result) {
+ startBackup();
+ }
+
+ @Override
+ public void onCryptoOperationError(ExportResult result) {
+ result.createNotify(getActivity()).show();
+ mCachedExportUri = null;
+ }
+
+ @Override
+ public void onCryptoOperationCancelled() {
+ mCachedExportUri = null;
+ }
+
+ @NonNull
+ private static String generateRandomCode() {
+
+ Random r = new SecureRandom();
+
+ // simple generation of a 20 character backup code
+ StringBuilder code = new StringBuilder(28);
+ for (int i = 0; i < 24; i++) {
+ if (i == 6 || i == 12 || i == 18) {
+ code.append('-');
+ }
+ code.append((char) ('A' + r.nextInt(26)));
+ }
+
+ return code.toString();
+
+ }
+
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java
index 739eb3e35..b79e4454d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java
@@ -37,17 +37,17 @@ import org.spongycastle.bcpg.sig.KeyFlags;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
-import org.sufficientlysecure.keychain.operations.results.ExportResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
+import org.sufficientlysecure.keychain.operations.results.UploadResult;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
-import org.sufficientlysecure.keychain.service.ExportKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
+import org.sufficientlysecure.keychain.service.UploadKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
@@ -69,7 +69,7 @@ public class CreateKeyFinalFragment extends Fragment {
SaveKeyringParcel mSaveKeyringParcel;
- private CryptoOperationHelper<ExportKeyringParcel, ExportResult> mUploadOpHelper;
+ private CryptoOperationHelper<UploadKeyringParcel, UploadResult> mUploadOpHelper;
private CryptoOperationHelper<SaveKeyringParcel, EditKeyResult> mCreateOpHelper;
private CryptoOperationHelper<SaveKeyringParcel, EditKeyResult> mMoveToCardOpHelper;
@@ -407,20 +407,20 @@ public class CreateKeyFinalFragment extends Fragment {
}
// set data uri as path to keyring
- final Uri blobUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(saveKeyResult.mMasterKeyId);
+ final long masterKeyId = saveKeyResult.mMasterKeyId;
// upload to favorite keyserver
final String keyserver = Preferences.getPreferences(activity).getPreferredKeyserver();
- CryptoOperationHelper.Callback<ExportKeyringParcel, ExportResult> callback
- = new CryptoOperationHelper.Callback<ExportKeyringParcel, ExportResult>() {
+ CryptoOperationHelper.Callback<UploadKeyringParcel, UploadResult> callback
+ = new CryptoOperationHelper.Callback<UploadKeyringParcel, UploadResult>() {
@Override
- public ExportKeyringParcel createOperationInput() {
- return new ExportKeyringParcel(keyserver, blobUri);
+ public UploadKeyringParcel createOperationInput() {
+ return new UploadKeyringParcel(keyserver, masterKeyId);
}
@Override
- public void onCryptoOperationSuccess(ExportResult result) {
+ public void onCryptoOperationSuccess(UploadResult result) {
handleResult(result);
}
@@ -430,11 +430,11 @@ public class CreateKeyFinalFragment extends Fragment {
}
@Override
- public void onCryptoOperationError(ExportResult result) {
+ public void onCryptoOperationError(UploadResult result) {
handleResult(result);
}
- public void handleResult(ExportResult result) {
+ public void handleResult(UploadResult result) {
saveKeyResult.getLog().add(result, 0);
finishWithResult(saveKeyResult);
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerBackupFragment.java
index a3ea8ad9a..cf47dfc94 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerBackupFragment.java
@@ -18,11 +18,7 @@
package org.sufficientlysecure.keychain.ui;
-import java.io.File;
-import java.text.SimpleDateFormat;
import java.util.ArrayList;
-import java.util.Date;
-import java.util.Locale;
import android.app.Activity;
import android.content.ContentResolver;
@@ -37,13 +33,11 @@ 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.pgp.CanonicalizedSecretKey.SecretKeyType;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
-import org.sufficientlysecure.keychain.util.ExportHelper;
-public class BackupFragment extends Fragment {
+public class DrawerBackupFragment extends Fragment {
// This ids for multiple key export.
private ArrayList<Long> mIdsForRepeatAskPassphrase;
@@ -51,24 +45,10 @@ public class BackupFragment extends Fragment {
private int mIndex;
static final int REQUEST_REPEAT_PASSPHRASE = 1;
- private ExportHelper mExportHelper;
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- // we won't get attached to a non-fragment activity, so the cast should be safe
- mExportHelper = new ExportHelper((FragmentActivity) activity);
- }
-
- @Override
- public void onDetach() {
- super.onDetach();
- mExportHelper = null;
- }
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.backup_fragment, container, false);
+ View view = inflater.inflate(R.layout.drawer_backup_fragment, container, false);
View backupAll = view.findViewById(R.id.backup_all);
View backupPublicKeys = view.findViewById(R.id.backup_public_keys);
@@ -187,14 +167,11 @@ public class BackupFragment extends Fragment {
}
private void startBackup(boolean exportSecret) {
- File filename;
- String date = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
- if (exportSecret) {
- filename = new File(Constants.Path.APP_DIR, "keys_" + date + ".asc");
- } else {
- filename = new File(Constants.Path.APP_DIR, "keys_" + date + ".pub.asc");
- }
- mExportHelper.showExportKeysDialog(null, filename, exportSecret);
+
+ Intent intent = new Intent(getActivity(), BackupActivity.class);
+ intent.putExtra(BackupActivity.EXTRA_SECRET, exportSecret);
+ startActivity(intent);
+
}
}
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 6f5d98afd..a5bd84d7e 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java
@@ -204,7 +204,7 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
private void onBackupSelected() {
mToolbar.setTitle(R.string.nav_backup);
mDrawer.setSelectionByIdentifier(ID_APPS, false);
- Fragment frag = new BackupFragment();
+ Fragment frag = new DrawerBackupFragment();
setFragment(frag, true);
}
@@ -265,7 +265,7 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
} else if (frag instanceof AppsListFragment) {
mToolbar.setTitle(R.string.nav_apps);
mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_APPS), false);
- } else if (frag instanceof BackupFragment) {
+ } else if (frag instanceof DrawerBackupFragment) {
mToolbar.setTitle(R.string.nav_backup);
mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_BACKUP), false);
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java
index 0415128a2..f38e4928d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java
@@ -17,6 +17,7 @@
package org.sufficientlysecure.keychain.ui;
+
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
@@ -29,10 +30,12 @@ import android.widget.Spinner;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.operations.results.ExportResult;
+import org.sufficientlysecure.keychain.operations.results.UploadResult;
+import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
-import org.sufficientlysecure.keychain.service.ExportKeyringParcel;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.service.UploadKeyringParcel;
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
import org.sufficientlysecure.keychain.util.Log;
@@ -42,7 +45,7 @@ import org.sufficientlysecure.keychain.util.Preferences;
* Sends the selected public key to a keyserver
*/
public class UploadKeyActivity extends BaseActivity
- implements CryptoOperationHelper.Callback<ExportKeyringParcel, ExportResult> {
+ implements CryptoOperationHelper.Callback<UploadKeyringParcel, UploadResult> {
private View mUploadButton;
private Spinner mKeyServerSpinner;
@@ -50,8 +53,8 @@ public class UploadKeyActivity extends BaseActivity
// CryptoOperationHelper.Callback vars
private String mKeyserver;
- private Uri mUnifiedKeyringUri;
- private CryptoOperationHelper<ExportKeyringParcel, ExportResult> mUploadOpHelper;
+ private long mMasterKeyId;
+ private CryptoOperationHelper<UploadKeyringParcel, UploadResult> mUploadOpHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -85,6 +88,16 @@ public class UploadKeyActivity extends BaseActivity
finish();
return;
}
+
+ try {
+ mMasterKeyId = new ProviderHelper(this).getCachedPublicKeyRing(
+ KeyRings.buildUnifiedKeyRingUri(mDataUri)).getMasterKeyId();
+ } catch (PgpKeyNotFoundException e) {
+ Log.e(Constants.TAG, "Intent data pointed to bad key!");
+ finish();
+ return;
+ }
+
}
@Override
@@ -101,13 +114,10 @@ public class UploadKeyActivity extends BaseActivity
}
private void uploadKey() {
- Uri blobUri = KeyRings.buildUnifiedKeyRingUri(mDataUri);
- mUnifiedKeyringUri = blobUri;
-
String server = (String) mKeyServerSpinner.getSelectedItem();
mKeyserver = server;
- mUploadOpHelper = new CryptoOperationHelper(1, this, this, R.string.progress_uploading);
+ mUploadOpHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_uploading);
mUploadOpHelper.cryptoOperation();
}
@@ -125,12 +135,12 @@ public class UploadKeyActivity extends BaseActivity
}
@Override
- public ExportKeyringParcel createOperationInput() {
- return new ExportKeyringParcel(mKeyserver, mUnifiedKeyringUri);
+ public UploadKeyringParcel createOperationInput() {
+ return new UploadKeyringParcel(mKeyserver, mMasterKeyId);
}
@Override
- public void onCryptoOperationSuccess(ExportResult result) {
+ public void onCryptoOperationSuccess(UploadResult result) {
result.createNotify(this).show();
}
@@ -140,7 +150,7 @@ public class UploadKeyActivity extends BaseActivity
}
@Override
- public void onCryptoOperationError(ExportResult result) {
+ public void onCryptoOperationError(UploadResult result) {
result.createNotify(this).show();
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java
index 18e830139..bd611e6bb 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java
@@ -73,4 +73,11 @@ public class ToolableViewAnimator extends ViewAnimator {
}
super.addView(child, index, params);
}
+
+ @Override
+ public void setDisplayedChild(int whichChild) {
+ if (whichChild != getDisplayedChild()) {
+ super.setDisplayedChild(whichChild);
+ }
+ }
}
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 f2ce456f6..cc90c173f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java
@@ -97,7 +97,7 @@ public class ExportHelper
@Override
public ExportKeyringParcel createOperationInput() {
- return new ExportKeyringParcel(mMasterKeyIds, mExportSecret, mExportFile.getAbsolutePath());
+ return new ExportKeyringParcel(null, mMasterKeyIds, mExportSecret, Uri.fromFile(mExportFile));
}
@Override
diff --git a/OpenKeychain/src/main/res/anim/fade_in_delayed.xml b/OpenKeychain/src/main/res/anim/fade_in_delayed.xml
new file mode 100644
index 000000000..3f2887b5e
--- /dev/null
+++ b/OpenKeychain/src/main/res/anim/fade_in_delayed.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+ android:interpolator="@android:anim/bounce_interpolator"
+ android:duration="700"
+ android:startOffset="400"
+ />
+</set>
diff --git a/OpenKeychain/src/main/res/anim/fade_out_delayed.xml b/OpenKeychain/src/main/res/anim/fade_out_delayed.xml
new file mode 100644
index 000000000..a895bdedd
--- /dev/null
+++ b/OpenKeychain/src/main/res/anim/fade_out_delayed.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
+ android:interpolator="@android:anim/accelerate_interpolator"
+ android:duration="300"
+ android:startOffset="400"
+ />
+</set>
diff --git a/OpenKeychain/src/main/res/layout/backup_activity.xml b/OpenKeychain/src/main/res/layout/backup_activity.xml
new file mode 100644
index 000000000..59ab6cbf2
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/backup_activity.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <include
+ android:id="@+id/toolbar_include"
+ layout="@layout/toolbar_standalone_white" />
+
+ <LinearLayout
+ android:layout_below="@id/toolbar_include"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <include layout="@layout/notify_area" />
+
+ <FrameLayout
+ android:id="@+id/content_frame"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ </FrameLayout>
+
+ </LinearLayout>
+</RelativeLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/backup_code_entry_fragment.xml b/OpenKeychain/src/main/res/layout/backup_code_entry_fragment.xml
new file mode 100644
index 000000000..8454b8d40
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/backup_code_entry_fragment.xml
@@ -0,0 +1,329 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ xmlns:custom="http://schemas.android.com/apk/res-auto"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingTop="50dp">
+
+ <org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_gravity="center_horizontal"
+ android:id="@+id/title_animator"
+ android:inAnimation="@anim/fade_in"
+ android:outAnimation="@anim/fade_out"
+ custom:initialView="0">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="10dp"
+ android:layout_gravity="center_horizontal"
+ android:gravity="center_horizontal"
+ android:text="The backup will be secured with a backup code. Write it down before you proceed!"
+ style="?android:textAppearanceMedium"
+ />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="10dp"
+ android:layout_gravity="center_horizontal"
+ android:gravity="center_horizontal"
+ android:text="Please enter the backup code:"
+ style="?android:textAppearanceMedium"
+ />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="10dp"
+ android:layout_gravity="center_horizontal"
+ android:gravity="center_horizontal"
+ android:text="Code accepted!"
+ style="?android:textAppearanceMedium"
+ />
+
+ </org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator>
+
+ <org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_gravity="center_horizontal"
+ android:id="@+id/code_animator"
+ android:inAnimation="@anim/fade_in"
+ android:outAnimation="@anim/fade_out"
+ android:layout_marginTop="15dp"
+ android:layout_marginBottom="15dp"
+ custom:initialView="0">
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:id="@+id/backup_code_display_1"
+ android:textStyle="bold"
+ android:typeface="monospace"
+ android:textSize="@dimen/abc_text_size_medium_material"
+ style="@android:style/Widget.EditText"
+ android:clickable="false"
+ android:focusable="false"
+ tools:text="ABCDEF"
+ />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:textStyle="bold"
+ android:typeface="monospace"
+ android:textSize="@dimen/abc_text_size_medium_material"
+ android:text="-"
+ />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:id="@+id/backup_code_display_2"
+ android:textStyle="bold"
+ android:typeface="monospace"
+ android:textSize="@dimen/abc_text_size_medium_material"
+ style="@android:style/Widget.EditText"
+ android:clickable="false"
+ android:focusable="false"
+ tools:text="GHIJKL"
+ />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:textStyle="bold"
+ android:typeface="monospace"
+ android:textSize="@dimen/abc_text_size_medium_material"
+ android:text="-"
+ />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:id="@+id/backup_code_display_3"
+ android:textStyle="bold"
+ android:typeface="monospace"
+ android:textSize="@dimen/abc_text_size_medium_material"
+ style="@android:style/Widget.EditText"
+ android:clickable="false"
+ android:focusable="false"
+ tools:text="MNOPQR"
+ />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:textStyle="bold"
+ android:typeface="monospace"
+ android:textSize="@dimen/abc_text_size_medium_material"
+ android:text="-"
+ />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:id="@+id/backup_code_display_4"
+ android:textStyle="bold"
+ android:typeface="monospace"
+ android:textSize="@dimen/abc_text_size_medium_material"
+ android:singleLine="true"
+ style="@android:style/Widget.EditText"
+ android:clickable="false"
+ android:focusable="false"
+ tools:text="STUVWX"
+ />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ >
+
+ <EditText
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:id="@+id/backup_code_1"
+ android:textStyle="bold"
+ android:typeface="monospace"
+ android:textSize="@dimen/abc_text_size_medium_material"
+ android:singleLine="true"
+ android:inputType="textNoSuggestions|textCapCharacters"
+ android:hint="ABCDEF"
+ android:textColorHint="@android:color/transparent"
+ android:maxLength="6"
+ />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:textStyle="bold"
+ android:typeface="monospace"
+ android:textSize="@dimen/abc_text_size_medium_material"
+ android:text="-"
+ />
+
+ <EditText
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:id="@+id/backup_code_2"
+ android:textStyle="bold"
+ android:typeface="monospace"
+ android:textSize="@dimen/abc_text_size_medium_material"
+ android:singleLine="true"
+ android:inputType="textNoSuggestions|textCapCharacters"
+ android:hint="ABCDEF"
+ android:textColorHint="@android:color/transparent"
+ android:maxLength="6"
+ />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:textStyle="bold"
+ android:typeface="monospace"
+ android:textSize="@dimen/abc_text_size_medium_material"
+ android:text="-"
+ />
+
+ <EditText
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:id="@+id/backup_code_3"
+ android:textStyle="bold"
+ android:typeface="monospace"
+ android:textSize="@dimen/abc_text_size_medium_material"
+ android:singleLine="true"
+ android:inputType="textNoSuggestions|textCapCharacters"
+ android:hint="ABCDEF"
+ android:textColorHint="@android:color/transparent"
+ android:maxLength="6"
+ />
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:textStyle="bold"
+ android:typeface="monospace"
+ android:textSize="@dimen/abc_text_size_medium_material"
+ android:text="-"
+ />
+
+ <EditText
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:id="@+id/backup_code_4"
+ android:textStyle="bold"
+ android:typeface="monospace"
+ android:textSize="@dimen/abc_text_size_medium_material"
+ android:singleLine="true"
+ android:inputType="textNoSuggestions|textCapCharacters"
+ android:hint="ABCDEF"
+ android:textColorHint="@android:color/transparent"
+ android:maxLength="6"
+ />
+
+ </LinearLayout>
+
+ </org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator>
+
+ <org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_gravity="center_horizontal"
+ android:id="@+id/status_animator"
+ android:inAnimation="@anim/fade_in_delayed"
+ android:outAnimation="@anim/fade_out"
+ custom:initialView="0">
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:layout_margin="10dp"
+ android:text="Ok, I wrote it down!"
+ android:padding="12dp"
+ android:id="@+id/button_backup_input"
+ style="?android:buttonBarButtonStyle"
+ />
+
+ <Space
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:text="The backup code you entered is wrong!\nDid you write it down correctly?"
+ style="?android:textAppearanceMedium"
+ />
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="?android:buttonBarStyle">
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="10dp"
+ android:padding="12dp"
+ android:text="Share backup"
+ android:drawableLeft="@drawable/ic_share_grey_24dp"
+ android:drawablePadding="8dp"
+ android:id="@+id/button_backup_share"
+ style="?android:buttonBarButtonStyle"
+ />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="10dp"
+ android:padding="12dp"
+ android:text="Save backup"
+ android:drawableLeft="@drawable/ic_save_grey_24dp"
+ android:drawablePadding="8dp"
+ android:id="@+id/button_backup_save"
+ style="?android:buttonBarButtonStyle"
+ />
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ </org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator>
+
+
+</LinearLayout>
diff --git a/OpenKeychain/src/main/res/layout/backup_fragment.xml b/OpenKeychain/src/main/res/layout/drawer_backup_fragment.xml
index 96fba954b..96fba954b 100644
--- a/OpenKeychain/src/main/res/layout/backup_fragment.xml
+++ b/OpenKeychain/src/main/res/layout/drawer_backup_fragment.xml
diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml
index b0e0f5734..360ecb136 100644
--- a/OpenKeychain/src/main/res/values/strings.xml
+++ b/OpenKeychain/src/main/res/values/strings.xml
@@ -30,6 +30,7 @@
<string name="title_export_keys">"Backup Keys"</string>
<string name="title_key_not_found">"Key Not Found"</string>
<string name="title_send_key">"Upload to Keyserver"</string>
+ <string name="title_backup">"Backup Key"</string>
<string name="title_certify_key">"Confirm Key"</string>
<string name="title_key_details">"Key Details"</string>
<string name="title_help">"Help"</string>
@@ -1299,16 +1300,11 @@
<item quantity="one">"Exporting one key"</item>
<item quantity="other">"Exporting %d keys"</item>
</plurals>
- <string name="msg_export_file_name">"Filename: %s"</string>
<string name="msg_export_all">"Exporting all keys"</string>
<string name="msg_export_public">"Exporting public key %s"</string>
<string name="msg_export_upload_public">"Uploading public key %s"</string>
<string name="msg_export_secret">"Exporting secret key %s"</string>
- <string name="msg_export_error_no_file">"No filename specified!"</string>
- <string name="msg_export_error_fopen">"Error opening file!"</string>
- <string name="msg_export_error_no_uri">"No URI specified!"</string>
<string name="msg_export_error_uri_open">"Error opening URI stream!"</string>
- <string name="msg_export_error_storage">"Storage is not ready for writing!"</string>
<string name="msg_export_error_db">"Database error!"</string>
<string name="msg_export_error_io">"Input/output error!"</string>
<string name="msg_export_error_key">"Error preprocessing key data!"</string>
diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/ExportTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/ExportTest.java
index a659dc7da..4d4915e8d 100644
--- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/ExportTest.java
+++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/ExportTest.java
@@ -17,40 +17,65 @@
package org.sufficientlysecure.keychain.operations;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.io.PrintStream;
+import java.security.Security;
+import java.util.Iterator;
+
+import android.app.Application;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.net.Uri;
+
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.Robolectric;
import org.robolectric.RobolectricGradleTestRunner;
-import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLog;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.sufficientlysecure.keychain.WorkaroundBuildConfig;
-import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
+import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.operations.results.ExportResult;
+import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
+import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
+import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
+import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation;
import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow;
import org.sufficientlysecure.keychain.pgp.WrappedSignature;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
+import org.sufficientlysecure.keychain.service.ExportKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.ProgressScaler;
import org.sufficientlysecure.keychain.util.TestingUtils;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.PrintStream;
-import java.security.Security;
-import java.util.Iterator;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = WorkaroundBuildConfig.class, sdk = 21, manifest = "src/main/AndroidManifest.xml")
@@ -84,7 +109,7 @@ public class ExportTest {
parcel.mNewUnlock = new ChangeUnlockParcel(mKeyPhrase1);
PgpEditKeyResult result = op.createSecretKeyRing(parcel);
- Assert.assertTrue("initial test key creation must succeed", result.success());
+ assertTrue("initial test key creation must succeed", result.success());
Assert.assertNotNull("initial test key creation must succeed", result.getRing());
mStaticRing1 = result.getRing();
@@ -102,7 +127,7 @@ public class ExportTest {
parcel.mNewUnlock = new ChangeUnlockParcel(null, new Passphrase("1234"));
PgpEditKeyResult result = op.createSecretKeyRing(parcel);
- Assert.assertTrue("initial test key creation must succeed", result.success());
+ assertTrue("initial test key creation must succeed", result.success());
Assert.assertNotNull("initial test key creation must succeed", result.getRing());
mStaticRing2 = result.getRing();
@@ -125,17 +150,17 @@ public class ExportTest {
}
@Test
- public void testExportAll() throws Exception {
+ public void testExportAllLocalStripped() throws Exception {
ExportOperation op = new ExportOperation(RuntimeEnvironment.application,
new ProviderHelper(RuntimeEnvironment.application), null);
// make sure there is a local cert (so the later checks that there are none are meaningful)
- Assert.assertTrue("second keyring has local certification", checkForLocal(mStaticRing2));
+ assertTrue("second keyring has local certification", checkForLocal(mStaticRing2));
ByteArrayOutputStream out = new ByteArrayOutputStream();
- ExportResult result = op.exportKeyRings(new OperationLog(), null, false, out);
+ boolean result = op.exportKeysToStream(new OperationLog(), null, false, out);
- Assert.assertTrue("export must be a success", result.success());
+ assertTrue("export must be a success", result);
long masterKeyId1, masterKeyId2;
if (mStaticRing1.getMasterKeyId() < mStaticRing2.getMasterKeyId()) {
@@ -150,70 +175,206 @@ public class ExportTest {
UncachedKeyRing.fromStream(new ByteArrayInputStream(out.toByteArray()));
{
- Assert.assertTrue("export must have two keys (1/2)", unc.hasNext());
+ assertTrue("export must have two keys (1/2)", unc.hasNext());
UncachedKeyRing ring = unc.next();
Assert.assertEquals("first exported key has correct masterkeyid",
masterKeyId1, ring.getMasterKeyId());
- Assert.assertFalse("first exported key must not be secret", ring.isSecret());
- Assert.assertFalse("there must be no local signatures in an exported keyring",
+ assertFalse("first exported key must not be secret", ring.isSecret());
+ assertFalse("there must be no local signatures in an exported keyring",
checkForLocal(ring));
}
{
- Assert.assertTrue("export must have two keys (2/2)", unc.hasNext());
+ assertTrue("export must have two keys (2/2)", unc.hasNext());
UncachedKeyRing ring = unc.next();
Assert.assertEquals("second exported key has correct masterkeyid",
masterKeyId2, ring.getMasterKeyId());
- Assert.assertFalse("second exported key must not be secret", ring.isSecret());
- Assert.assertFalse("there must be no local signatures in an exported keyring",
+ assertFalse("second exported key must not be secret", ring.isSecret());
+ assertFalse("there must be no local signatures in an exported keyring",
checkForLocal(ring));
}
out = new ByteArrayOutputStream();
- result = op.exportKeyRings(new OperationLog(), null, true, out);
+ result = op.exportKeysToStream(new OperationLog(), null, true, out);
- Assert.assertTrue("export must be a success", result.success());
+ assertTrue("export must be a success", result);
unc = UncachedKeyRing.fromStream(new ByteArrayInputStream(out.toByteArray()));
{
- Assert.assertTrue("export must have four keys (1/4)", unc.hasNext());
+ assertTrue("export must have four keys (1/4)", unc.hasNext());
UncachedKeyRing ring = unc.next();
Assert.assertEquals("1/4 exported key has correct masterkeyid",
masterKeyId1, ring.getMasterKeyId());
- Assert.assertFalse("1/4 exported key must not be public", ring.isSecret());
- Assert.assertFalse("there must be no local signatures in an exported keyring",
+ assertFalse("1/4 exported key must not be public", ring.isSecret());
+ assertFalse("there must be no local signatures in an exported keyring",
checkForLocal(ring));
- Assert.assertTrue("export must have four keys (2/4)", unc.hasNext());
+ assertTrue("export must have four keys (2/4)", unc.hasNext());
ring = unc.next();
Assert.assertEquals("2/4 exported key has correct masterkeyid",
masterKeyId1, ring.getMasterKeyId());
- Assert.assertTrue("2/4 exported key must be public", ring.isSecret());
- Assert.assertFalse("there must be no local signatures in an exported keyring",
+ assertTrue("2/4 exported key must be public", ring.isSecret());
+ assertFalse("there must be no local signatures in an exported keyring",
checkForLocal(ring));
}
{
- Assert.assertTrue("export must have four keys (3/4)", unc.hasNext());
+ assertTrue("export must have four keys (3/4)", unc.hasNext());
UncachedKeyRing ring = unc.next();
Assert.assertEquals("3/4 exported key has correct masterkeyid",
masterKeyId2, ring.getMasterKeyId());
- Assert.assertFalse("3/4 exported key must not be public", ring.isSecret());
- Assert.assertFalse("there must be no local signatures in an exported keyring",
+ assertFalse("3/4 exported key must not be public", ring.isSecret());
+ assertFalse("there must be no local signatures in an exported keyring",
checkForLocal(ring));
- Assert.assertTrue("export must have four keys (4/4)", unc.hasNext());
+ assertTrue("export must have four keys (4/4)", unc.hasNext());
ring = unc.next();
Assert.assertEquals("4/4 exported key has correct masterkeyid",
masterKeyId2, ring.getMasterKeyId());
- Assert.assertTrue("4/4 exported key must be public", ring.isSecret());
- Assert.assertFalse("there must be no local signatures in an exported keyring",
+ assertTrue("4/4 exported key must be public", ring.isSecret());
+ assertFalse("there must be no local signatures in an exported keyring",
checkForLocal(ring));
}
}
+ @Test
+ public void testExportUnencrypted() throws Exception {
+
+ ContentResolver mockResolver = mock(ContentResolver.class);
+
+ Uri fakeOutputUri = Uri.parse("content://fake/out/1");
+ ByteArrayOutputStream outStream1 = new ByteArrayOutputStream();
+ when(mockResolver.openOutputStream(fakeOutputUri)).thenReturn(outStream1);
+
+ Application spyApplication = spy(RuntimeEnvironment.application);
+ when(spyApplication.getContentResolver()).thenReturn(mockResolver);
+
+ ExportOperation op = new ExportOperation(spyApplication,
+ new ProviderHelper(RuntimeEnvironment.application), null);
+
+ ExportKeyringParcel parcel = new ExportKeyringParcel(null,
+ new long[] { mStaticRing1.getMasterKeyId() }, false, fakeOutputUri);
+
+ ExportResult result = op.execute(parcel, null);
+
+ verify(mockResolver).openOutputStream(fakeOutputUri);
+
+ assertTrue("export must succeed", result.success());
+
+ TestingUtils.assertArrayEqualsPrefix("exported data must start with ascii armor header",
+ "-----BEGIN PGP PUBLIC KEY BLOCK-----\n".getBytes(), outStream1.toByteArray());
+ TestingUtils.assertArrayEqualsSuffix("exported data must end with ascii armor header",
+ "-----END PGP PUBLIC KEY BLOCK-----\n".getBytes(), outStream1.toByteArray());
+
+ {
+ IteratorWithIOThrow<UncachedKeyRing> unc
+ = UncachedKeyRing.fromStream(new ByteArrayInputStream(outStream1.toByteArray()));
+
+ assertTrue("export must have one key", unc.hasNext());
+ UncachedKeyRing ring = unc.next();
+ Assert.assertEquals("exported key has correct masterkeyid",
+ mStaticRing1.getMasterKeyId(), ring.getMasterKeyId());
+ assertFalse("export must have exactly one key", unc.hasNext());
+ }
+
+ }
+
+ @Test
+ public void testExportEncrypted() throws Exception {
+
+
+ Application spyApplication;
+ ContentResolver mockResolver = mock(ContentResolver.class);
+
+ Uri fakePipedUri, fakeOutputUri;
+ ByteArrayOutputStream outStream; {
+
+ fakePipedUri = Uri.parse("content://fake/pipe/1");
+ PipedInputStream pipedInStream = new PipedInputStream(8192);
+ PipedOutputStream pipedOutStream = new PipedOutputStream(pipedInStream);
+ when(mockResolver.openOutputStream(fakePipedUri)).thenReturn(pipedOutStream);
+ when(mockResolver.openInputStream(fakePipedUri)).thenReturn(pipedInStream);
+ when(mockResolver.insert(eq(TemporaryStorageProvider.CONTENT_URI), any(ContentValues.class)))
+ .thenReturn(fakePipedUri);
+
+ fakeOutputUri = Uri.parse("content://fake/out/1");
+ outStream = new ByteArrayOutputStream();
+ when(mockResolver.openOutputStream(fakeOutputUri)).thenReturn(outStream);
+
+ spyApplication = spy(RuntimeEnvironment.application);
+ when(spyApplication.getContentResolver()).thenReturn(mockResolver);
+ }
+
+ Passphrase passphrase = new Passphrase("abcde");
+
+ { // export encrypted
+ ExportOperation op = new ExportOperation(spyApplication,
+ new ProviderHelper(RuntimeEnvironment.application), null);
+
+ ExportKeyringParcel parcel = new ExportKeyringParcel(passphrase,
+ new long[] { mStaticRing1.getMasterKeyId() }, false, fakeOutputUri);
+
+ ExportResult result = op.execute(parcel, null);
+
+ verify(mockResolver).openOutputStream(fakePipedUri);
+ verify(mockResolver).openInputStream(fakePipedUri);
+ verify(mockResolver).openOutputStream(fakeOutputUri);
+
+ assertTrue("export must succeed", result.success());
+ TestingUtils.assertArrayEqualsPrefix("exported data must start with ascii armor header",
+ "-----BEGIN PGP MESSAGE-----\n".getBytes(), outStream.toByteArray());
+ }
+
+ {
+ PgpDecryptVerifyOperation op = new PgpDecryptVerifyOperation(RuntimeEnvironment.application,
+ new ProviderHelper(RuntimeEnvironment.application), null);
+
+ PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(outStream.toByteArray());
+ input.setAllowSymmetricDecryption(true);
+
+ {
+ DecryptVerifyResult result = op.execute(input, new CryptoInputParcel());
+ assertTrue("decryption must return pending without passphrase", result.isPending());
+ Assert.assertTrue("should contain pending passphrase log entry",
+ result.getLog().containsType(LogType.MSG_DC_PENDING_PASSPHRASE));
+ }
+ {
+ DecryptVerifyResult result = op.execute(input, new CryptoInputParcel(new Passphrase("bad")));
+ assertFalse("decryption must fail with bad passphrase", result.success());
+ Assert.assertTrue("should contain bad passphrase log entry",
+ result.getLog().containsType(LogType.MSG_DC_ERROR_SYM_PASSPHRASE));
+ }
+
+ DecryptVerifyResult result = op.execute(input, new CryptoInputParcel(passphrase));
+ assertTrue("decryption must succeed with passphrase", result.success());
+
+ assertEquals("backup filename should be backup_keyid.pub.asc",
+ "backup_" + KeyFormattingUtils.convertKeyIdToHex(mStaticRing1.getMasterKeyId()) + ".pub.asc",
+ result.getDecryptionMetadata().getFilename());
+
+ TestingUtils.assertArrayEqualsPrefix("exported data must start with ascii armor header",
+ "-----BEGIN PGP PUBLIC KEY BLOCK-----\n".getBytes(), result.getOutputBytes());
+ TestingUtils.assertArrayEqualsSuffix("exported data must end with ascii armor header",
+ "-----END PGP PUBLIC KEY BLOCK-----\n".getBytes(), result.getOutputBytes());
+
+ {
+ IteratorWithIOThrow<UncachedKeyRing> unc
+ = UncachedKeyRing.fromStream(new ByteArrayInputStream(result.getOutputBytes()));
+
+ assertTrue("export must have one key", unc.hasNext());
+ UncachedKeyRing ring = unc.next();
+ Assert.assertEquals("exported key has correct masterkeyid",
+ mStaticRing1.getMasterKeyId(), ring.getMasterKeyId());
+ assertFalse("export must have exactly one key", unc.hasNext());
+ }
+
+ }
+
+ }
+
+
/** This function checks whether or not there are any local signatures in a keyring. */
private boolean checkForLocal(UncachedKeyRing ring) {
Iterator<WrappedSignature> sigs = ring.getPublicKey().getSignatures();
diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/util/TestingUtils.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/util/TestingUtils.java
index 1d7952464..0b35aaf22 100644
--- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/util/TestingUtils.java
+++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/util/TestingUtils.java
@@ -19,6 +19,9 @@ package org.sufficientlysecure.keychain.util;
import java.util.Random;
+import junit.framework.Assert;
+
+
public class TestingUtils {
public static Passphrase genPassphrase() {
return genPassphrase(false);
@@ -35,4 +38,25 @@ public class TestingUtils {
System.out.println("Generated passphrase: '" + passbuilder.toString() + "'");
return new Passphrase(passbuilder.toString());
}
+
+ public static void assertArrayEqualsPrefix(String msg, byte[] expected, byte[] actual) {
+
+ Assert.assertTrue("exepected must be shorter or equal to actual array length",
+ expected.length <= actual.length);
+ for (int i = 0; i < expected.length; i++) {
+ Assert.assertEquals(msg, expected[i], actual[i]);
+ }
+
+ }
+
+ public static void assertArrayEqualsSuffix(String msg, byte[] expected, byte[] actual) {
+
+ Assert.assertTrue("exepected must be shorter or equal to actual array length",
+ expected.length <= actual.length);
+ for (int i = 0; i < expected.length; i++) {
+ Assert.assertEquals(msg, expected[i], actual[actual.length -expected.length +i]);
+ }
+
+ }
+
}