export: support encrypted export, first version
This commit is contained in:
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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>() {
|
||||
|
||||
@@ -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>() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user