diff options
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations')
8 files changed, 427 insertions, 303 deletions
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/InputDataOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java index 7ec33874f..c48ccc500 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java @@ -135,7 +135,7 @@ public class InputDataOperation extends BaseOperation<InputDataParcel> { || contentType != null && !contentType.startsWith("multipart/") && !contentType.startsWith("text/") - && !contentType.startsWith("application/")) { + && !"application/octet-stream".equals(contentType)) { skipMimeParsing = true; } } 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 f959ddd76..24d1215d4 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 @@ -765,16 +765,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]; + } + }; + +} |