aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVincent Breitmoser <valodim@mugenguild.com>2015-09-28 00:03:11 +0200
committerVincent Breitmoser <valodim@mugenguild.com>2015-09-28 00:03:11 +0200
commit46e24058ba02b5302553c031d301484d5cbf2d18 (patch)
tree84e01476c494c24ae457d8c50eae5d225794fbab
parentdc9e068790ead0399e8515e1fc72c99e90b8cd70 (diff)
downloadopen-keychain-46e24058ba02b5302553c031d301484d5cbf2d18.tar.gz
open-keychain-46e24058ba02b5302553c031d301484d5cbf2d18.tar.bz2
open-keychain-46e24058ba02b5302553c031d301484d5cbf2d18.zip
export: support encrypted export, first version
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java89
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ExportResult.java21
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ExportKeyringParcel.java21
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeEntryFragment.java8
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java2
-rw-r--r--OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/ExportTest.java209
-rw-r--r--OpenKeychain/src/test/java/org/sufficientlysecure/keychain/util/TestingUtils.java24
7 files changed, 295 insertions, 79 deletions
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 2eefd869b..3ee5ff771 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java
@@ -19,16 +19,21 @@
package org.sufficientlysecure.keychain.operations;
-import java.io.BufferedOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.InputStream;
import java.io.OutputStream;
+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;
@@ -37,16 +42,21 @@ import org.sufficientlysecure.keychain.R;
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.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.ui.util.KeyFormattingUtils;
+import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
@@ -84,7 +94,7 @@ public class ExportOperation extends BaseOperation<ExportKeyringParcel> {
}
@NonNull
- public ExportResult execute(ExportKeyringParcel exportInput, CryptoInputParcel cryptoInput) {
+ public ExportResult execute(@NonNull ExportKeyringParcel exportInput, @Nullable CryptoInputParcel cryptoInput) {
OperationLog log = new OperationLog();
if (exportInput.mMasterKeyIds != null) {
@@ -94,25 +104,83 @@ public class ExportOperation extends BaseOperation<ExportKeyringParcel> {
}
try {
- OutputStream outStream = mProviderHelper.getContentResolver().openOutputStream(exportInput.mOutputUri);
- outStream = new BufferedOutputStream(outStream);
- return exportKeysToStream(log, exportInput.mMasterKeyIds, exportInput.mExportSecret, outStream);
+
+ boolean nonEncryptedOutput = exportInput.mSymmetricPassphrase == null;
+
+ Uri exportOutputUri = nonEncryptedOutput
+ ? exportInput.mOutputUri
+ : TemporaryStorageProvider.createFile(mContext);
+
+ { // export key data, and possibly return if we don't encrypt
+
+ OutputStream outStream = mContext.getContentResolver().openOutputStream(exportOutputUri);
+ boolean exportSuccess = exportKeysToStream(
+ log, exportInput.mMasterKeyIds, exportInput.mExportSecret, outStream);
+
+ 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);
+ }
+
+ if (nonEncryptedOutput) {
+ // log.add(LogType.MSG_EXPORT_NO_ENCRYPT, 1);
+ log.add(LogType.MSG_EXPORT_SUCCESS, 1);
+ return new ExportResult(ExportResult.RESULT_OK, log);
+ }
+ }
+
+ PgpSignEncryptOperation pseOp = new PgpSignEncryptOperation(mContext, mProviderHelper, mProgressable, mCancelled);
+
+ PgpSignEncryptInputParcel inputParcel = new PgpSignEncryptInputParcel();
+ inputParcel.setSymmetricPassphrase(exportInput.mSymmetricPassphrase);
+ inputParcel.setEnableAsciiArmorOutput(true);
+
+ InputStream inStream = mContext.getContentResolver().openInputStream(exportOutputUri);
+
+ String filename;
+ if (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";
+ }
+
+ InputData inputData = new InputData(inStream, 0L, filename);
+
+ OutputStream outStream = mContext.getContentResolver().openOutputStream(exportInput.mOutputUri);
+
+ 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);
+ }
+
+ log.add(encryptResult, 1);
+ log.add(LogType.MSG_EXPORT_SUCCESS, 1);
+ return new ExportResult(ExportResult.RESULT_OK, log);
+
} catch (FileNotFoundException e) {
log.add(LogType.MSG_EXPORT_ERROR_URI_OPEN, 1);
return new ExportResult(ExportResult.RESULT_ERROR, log);
+
}
}
- ExportResult exportKeysToStream(OperationLog log, long[] masterKeyIds, boolean exportSecret, OutputStream outStream) {
+ boolean exportKeysToStream(OperationLog log, long[] masterKeyIds, boolean exportSecret, OutputStream outStream) {
- int okSecret = 0, okPublic = 0, progress = 0;
+ // noinspection unused TODO use these in a log entry
+ int okSecret = 0, okPublic = 0;
+
+ int progress = 0;
Cursor cursor = queryForKeys(masterKeyIds);
if (cursor == null || !cursor.moveToFirst()) {
log.add(LogType.MSG_EXPORT_ERROR_DB, 1);
- return new ExportResult(ExportResult.RESULT_ERROR, log, okPublic, okSecret);
+ return false; // new ExportResult(ExportResult.RESULT_ERROR, log);
}
try {
@@ -148,7 +216,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 {
@@ -159,8 +227,7 @@ public class ExportOperation extends BaseOperation<ExportKeyringParcel> {
cursor.close();
}
- log.add(LogType.MSG_EXPORT_SUCCESS, 1);
- return new ExportResult(ExportResult.RESULT_OK, log, okPublic, okSecret);
+ return true;
}
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/service/ExportKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ExportKeyringParcel.java
index 0e990f24c..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,35 +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 Uri mOutputUri;
- public ExportKeyringParcel(String keyserver, UncachedKeyRing uncachedKeyRing) {
- mKeyserver = keyserver;
- mUncachedKeyRing = uncachedKeyRing;
- }
-
- @SuppressWarnings("unused") // TODO: is it used?
- public ExportKeyringParcel(long[] masterKeyIds, boolean exportSecret, Uri outputUri) {
+ 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;
mOutputUri = (Uri) in.readValue(Uri.class.getClassLoader());
mMasterKeyIds = in.createLongArray();
+ mSymmetricPassphrase = in.readParcelable(getClass().getClassLoader());
}
@Override
@@ -62,12 +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.writeValue(mOutputUri);
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/ui/BackupCodeEntryFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeEntryFragment.java
index 83c60e89d..2f32d4912 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeEntryFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeEntryFragment.java
@@ -98,6 +98,9 @@ public class BackupCodeEntryFragment extends Fragment implements OnBackStackChan
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);
@@ -281,7 +284,8 @@ public class BackupCodeEntryFragment extends Fragment implements OnBackStackChan
}
- private void animateFlashText(final TextView[] textViews, int color1, int color2, boolean staySecondColor) {
+ 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() {
@@ -299,7 +303,7 @@ public class BackupCodeEntryFragment extends Fragment implements OnBackStackChan
}
- private void setupEditTextFocusNext(final EditText[] backupCodes) {
+ private static void setupEditTextFocusNext(final EditText[] backupCodes) {
for (int i = 0; i < backupCodes.length -1; i++) {
final int next = i+1;
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 a25c75048..2e0b0af36 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java
@@ -96,7 +96,7 @@ public class ExportHelper
@Override
public ExportKeyringParcel createOperationInput() {
- return new ExportKeyringParcel(mMasterKeyIds, mExportSecret, Uri.fromFile(mExportFile));
+ return new ExportKeyringParcel(null, mMasterKeyIds, mExportSecret, Uri.fromFile(mExportFile));
}
@Override
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 305f7a7f8..935ff4b93 100644
--- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/ExportTest.java
+++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/ExportTest.java
@@ -17,6 +17,20 @@
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;
@@ -29,26 +43,39 @@ 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")
@@ -82,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();
@@ -100,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();
@@ -123,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.exportKeysToStream(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()) {
@@ -148,70 +175,190 @@ 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.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());
+
+ }
+
+ }
+
+
/** 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]);
+ }
+
+ }
+
}