diff options
Diffstat (limited to 'OpenKeychain/src')
5 files changed, 236 insertions, 141 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java index f8957cc0f..0bd8ada5a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java @@ -19,34 +19,37 @@ package org.sufficientlysecure.keychain.operations; import android.content.Context; -import android.os.Bundle; -import android.os.Environment; +import android.database.Cursor; +import android.net.Uri; import org.spongycastle.bcpg.ArmoredOutputStream; -import org.spongycastle.openpgp.PGPException; 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.keyimport.ParcelableKeyRing; +import org.sufficientlysecure.keychain.operations.results.ExportResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; -import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.PgpHelper; 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; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ProgressScaler; import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; @@ -92,6 +95,7 @@ public class ImportExportOperation extends BaseOperation { updateProgress(R.string.progress_importing, 0, 100); OperationLog log = new OperationLog(); + log.add(LogType.MSG_IMPORT, 0, num); // If there aren't even any keys, do nothing here. if (entries == null || !entries.hasNext()) { @@ -103,6 +107,7 @@ public class ImportExportOperation extends BaseOperation { int newKeys = 0, oldKeys = 0, badKeys = 0, secret = 0; ArrayList<Long> importedMasterKeyIds = new ArrayList<Long>(); + boolean cancelled = false; int position = 0; double progSteps = 100.0 / num; @@ -111,7 +116,8 @@ public class ImportExportOperation extends BaseOperation { ParcelableKeyRing entry = entries.next(); // Has this action been cancelled? If so, don't proceed any further - if (mCancelled != null && mCancelled.get()) { + if (checkCancelled()) { + cancelled = true; break; } @@ -166,7 +172,18 @@ public class ImportExportOperation extends BaseOperation { position++; } + // convert to long array + long[] importedMasterKeyIdsArray = new long[importedMasterKeyIds.size()]; + for (int i = 0; i < importedMasterKeyIds.size(); ++i) { + importedMasterKeyIdsArray[i] = importedMasterKeyIds.get(i); + } + int resultType = 0; + if (cancelled) { + log.add(LogType.MSG_OPERATION_CANCELLED, 1); + resultType |= ImportKeyResult.RESULT_CANCELLED; + } + // special return case: no new keys at all if (badKeys == 0 && newKeys == 0 && oldKeys == 0) { resultType = ImportKeyResult.RESULT_FAIL_NOTHING; @@ -187,93 +204,178 @@ public class ImportExportOperation extends BaseOperation { resultType |= ImportKeyResult.RESULT_WARNINGS; } } - if (mCancelled != null && mCancelled.get()) { - log.add(LogType.MSG_OPERATION_CANCELLED, 0); - resultType |= ImportKeyResult.RESULT_CANCELLED; - } - // convert to long array - long[] importedMasterKeyIdsArray = new long[importedMasterKeyIds.size()]; - for (int i = 0; i < importedMasterKeyIds.size(); ++i) { - importedMasterKeyIdsArray[i] = importedMasterKeyIds.get(i); + // Final log entry, it's easier to do this individually + if ( (newKeys > 0 || oldKeys > 0) && badKeys > 0) { + log.add(LogType.MSG_IMPORT_PARTIAL, 1); + } else if (newKeys > 0 || oldKeys > 0) { + log.add(LogType.MSG_IMPORT_SUCCESS, 1); + } else { + log.add(LogType.MSG_IMPORT_ERROR, 1); } return new ImportKeyResult(resultType, log, newKeys, oldKeys, badKeys, secret, importedMasterKeyIdsArray); } - public Bundle exportKeyRings(ArrayList<Long> publicKeyRingMasterIds, - ArrayList<Long> secretKeyRingMasterIds, - OutputStream outStream) throws PgpGeneralException, - PGPException, IOException { - Bundle returnData = new Bundle(); + public ExportResult exportToFile(long[] masterKeyIds, boolean exportSecret, String outputFile) { - int masterKeyIdsSize = publicKeyRingMasterIds.size() + secretKeyRingMasterIds.size(); - int progress = 0; + OperationLog log = new OperationLog(); + if (masterKeyIds != null) { + log.add(LogType.MSG_EXPORT, 0, masterKeyIds.length); + } else { + log.add(LogType.MSG_EXPORT_ALL, 0); + } - updateProgress( - mContext.getResources().getQuantityString(R.plurals.progress_exporting_key, - masterKeyIdsSize), 0, 100); + // 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); + } - if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - throw new PgpGeneralException( - mContext.getString(R.string.error_external_storage_not_ready)); + // check if storage is ready + if (!FileHelper.isStorageMounted(outputFile)) { + log.add(LogType.MSG_EXPORT_ERROR_STORAGE, 1); + return new ExportResult(ExportResult.RESULT_ERROR, log); } - // For each public masterKey id - for (long pubKeyMasterId : publicKeyRingMasterIds) { - progress++; - // Create an output stream - ArmoredOutputStream arOutStream = new ArmoredOutputStream(outStream); - String version = PgpHelper.getVersionForHeader(mContext); - if (version != null) { - arOutStream.setHeader("Version", version); + + try { + OutputStream outStream = new FileOutputStream(outputFile); + ExportResult result = exportKeyRings(log, masterKeyIds, exportSecret, outStream); + if (result.cancelled()) { + //noinspection ResultOfMethodCallIgnored + new File(outputFile).delete(); } + return result; + } catch (FileNotFoundException e) { + log.add(LogType.MSG_EXPORT_ERROR_FOPEN, 1); + return new ExportResult(ExportResult.RESULT_ERROR, log); + } + + } - updateProgress(progress * 100 / masterKeyIdsSize, 100); + public ExportResult exportToUri(long[] masterKeyIds, boolean exportSecret, Uri outputUri) { - try { - CanonicalizedPublicKeyRing ring = mProviderHelper.getCanonicalizedPublicKeyRing( - KeychainContract.KeyRings.buildUnifiedKeyRingUri(pubKeyMasterId) - ); - - ring.encode(arOutStream); - } catch (ProviderHelper.NotFoundException e) { - Log.e(Constants.TAG, "key not found!", e); - // TODO: inform user? - } + OperationLog log = new OperationLog(); + if (masterKeyIds != null) { + log.add(LogType.MSG_EXPORT, 0, masterKeyIds.length); + } else { + log.add(LogType.MSG_EXPORT_ALL, 0); + } + + // 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); + } - arOutStream.close(); + 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); } - // For each secret masterKey id - for (long secretKeyMasterId : secretKeyRingMasterIds) { - progress++; - // Create an output stream - ArmoredOutputStream arOutStream = new ArmoredOutputStream(outStream); - String version = PgpHelper.getVersionForHeader(mContext); - if (version != null) { - arOutStream.setHeader("Version", version); + } + + private ExportResult exportKeyRings(OperationLog log, long[] masterKeyIds, boolean exportSecret, + OutputStream outStream) { + + /* 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); + return new ExportResult(ExportResult.RESULT_ERROR, log); + } + */ + + int okSecret = 0, okPublic = 0, progress = 0; + + try { + + String selection = null, ids[] = null; + + if (masterKeyIds != null) { + // generate placeholders and string selection args + ids = new String[masterKeyIds.length]; + StringBuilder placeholders = new StringBuilder("?"); + for (int i = 0; i < masterKeyIds.length; i++) { + ids[i] = Long.toString(masterKeyIds[i]); + if (i != 0) { + placeholders.append(",?"); + } + } + + // put together selection string + selection = Tables.KEY_RINGS_PUBLIC + "." + KeyRings.MASTER_KEY_ID + + " IN (" + placeholders + ")"; } - updateProgress(progress * 100 / masterKeyIdsSize, 100); + Cursor cursor = mProviderHelper.getContentResolver().query( + KeyRings.buildUnifiedKeyRingsUri(), new String[]{ + KeyRings.MASTER_KEY_ID, KeyRings.PUBKEY_DATA, + KeyRings.PRIVKEY_DATA, KeyRings.HAS_ANY_SECRET + }, selection, ids, Tables.KEYS + "." + KeyRings.MASTER_KEY_ID + ); - try { - CanonicalizedSecretKeyRing secretKeyRing = - mProviderHelper.getCanonicalizedSecretKeyRing(secretKeyMasterId); - secretKeyRing.encode(arOutStream); - } catch (ProviderHelper.NotFoundException e) { - Log.e(Constants.TAG, "key not found!", e); - // TODO: inform user? + if (cursor == null || !cursor.moveToFirst()) { + log.add(LogType.MSG_EXPORT_ERROR_DB, 1); + return new ExportResult(ExportResult.RESULT_ERROR, log, okPublic, okSecret); } - arOutStream.close(); + int numKeys = cursor.getCount(); + + updateProgress( + mContext.getResources().getQuantityString(R.plurals.progress_exporting_key, + numKeys), 0, numKeys); + + // For each public masterKey id + while (!cursor.isAfterLast()) { + + // Create an output stream + ArmoredOutputStream arOutStream = new ArmoredOutputStream(outStream); + String version = PgpHelper.getVersionForHeader(mContext); + if (version != null) { + arOutStream.setHeader("Version", version); + } + + long keyId = cursor.getLong(0); + + log.add(LogType.MSG_EXPORT_PUBLIC, 1, KeyFormattingUtils.beautifyKeyId(keyId)); + + { // export public key part + byte[] data = cursor.getBlob(1); + arOutStream.write(data); + arOutStream.close(); + + okPublic += 1; + } + + // export secret key part + if (exportSecret && cursor.getInt(3) > 0) { + log.add(LogType.MSG_EXPORT_SECRET, 2, KeyFormattingUtils.beautifyKeyId(keyId)); + byte[] data = cursor.getBlob(2); + arOutStream.write(data); + + okSecret += 1; + } + + updateProgress(progress++, numKeys); + + cursor.moveToNext(); + } + + updateProgress(R.string.progress_done, numKeys, numKeys); + + } catch (IOException e) { + log.add(LogType.MSG_EXPORT_ERROR_IO, 1); + return new ExportResult(ExportResult.RESULT_ERROR, log, okPublic, okSecret); } - returnData.putInt(KeychainIntentService.RESULT_EXPORT, masterKeyIdsSize); - updateProgress(R.string.progress_done, 100, 100); + log.add(LogType.MSG_EXPORT_SUCCESS, 1); + return new ExportResult(ExportResult.RESULT_OK, log, okPublic, okSecret); - return returnData; } } 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 dc47942fc..3a7328d5a 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 @@ -580,6 +580,24 @@ public abstract class OperationResult implements Parcelable { MSG_CRT_WARN_CERT_FAILED (LogLevel.WARN, R.string.msg_crt_warn_cert_failed), MSG_CRT_WARN_SAVE_FAILED (LogLevel.WARN, R.string.msg_crt_warn_save_failed), + MSG_IMPORT (LogLevel.START, R.plurals.msg_import), + MSG_IMPORT_ERROR (LogLevel.ERROR, R.string.msg_import_error), + MSG_IMPORT_PARTIAL (LogLevel.ERROR, R.string.msg_import_partial), + MSG_IMPORT_SUCCESS (LogLevel.OK, R.string.msg_import_success), + + MSG_EXPORT (LogLevel.START, R.plurals.msg_export), + 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_SUCCESS (LogLevel.OK, R.string.msg_export_success), + MSG_CRT_UPLOAD_SUCCESS (LogLevel.OK, R.string.msg_crt_upload_success), MSG_ACC_SAVED (LogLevel.INFO, R.string.api_settings_save_msg), diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java index 0dcfa1721..f156ef266 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -32,6 +32,7 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.CertifyOperation; import org.sufficientlysecure.keychain.operations.DeleteOperation; import org.sufficientlysecure.keychain.operations.results.DeleteResult; +import org.sufficientlysecure.keychain.operations.results.ExportResult; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; import org.sufficientlysecure.keychain.operations.results.CertifyResult; @@ -356,12 +357,15 @@ public class KeychainIntentService extends IntentService implements Progressable } else if (ACTION_DELETE.equals(action)) { + // Input long[] masterKeyIds = data.getLongArray(DELETE_KEY_LIST); boolean isSecret = data.getBoolean(DELETE_IS_SECRET); + // Operation DeleteOperation op = new DeleteOperation(this, new ProviderHelper(this), this); DeleteResult result = op.execute(masterKeyIds, isSecret); + // Result sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); } else if (ACTION_DELETE_FILE_SECURELY.equals(action)) { @@ -523,72 +527,27 @@ public class KeychainIntentService extends IntentService implements Progressable } else if (ACTION_EXPORT_KEYRING.equals(action)) { - try { - - boolean exportSecret = data.getBoolean(EXPORT_SECRET, false); - long[] masterKeyIds = data.getLongArray(EXPORT_KEY_RING_MASTER_KEY_ID); - String outputFile = data.getString(EXPORT_FILENAME); - Uri outputUri = data.getParcelable(EXPORT_URI); - - // If not exporting all keys get the masterKeyIds of the keys to export from the intent - boolean exportAll = data.getBoolean(EXPORT_ALL); - - if (outputFile != null) { - // check if storage is ready - if (!FileHelper.isStorageMounted(outputFile)) { - throw new PgpGeneralException(getString(R.string.error_external_storage_not_ready)); - } - } - - ArrayList<Long> publicMasterKeyIds = new ArrayList<Long>(); - ArrayList<Long> secretMasterKeyIds = new ArrayList<Long>(); - - String selection = null; - if (!exportAll) { - selection = KeychainDatabase.Tables.KEYS + "." + KeyRings.MASTER_KEY_ID + " IN( "; - for (long l : masterKeyIds) { - selection += Long.toString(l) + ","; - } - selection = selection.substring(0, selection.length() - 1) + " )"; - } - - Cursor cursor = getContentResolver().query(KeyRings.buildUnifiedKeyRingsUri(), - new String[]{KeyRings.MASTER_KEY_ID, KeyRings.HAS_ANY_SECRET}, - selection, null, null); - try { - if (cursor != null && cursor.moveToFirst()) do { - // export public either way - publicMasterKeyIds.add(cursor.getLong(0)); - // add secret if available (and requested) - if (exportSecret && cursor.getInt(1) != 0) - secretMasterKeyIds.add(cursor.getLong(0)); - } while (cursor.moveToNext()); - } finally { - if (cursor != null) { - cursor.close(); - } - } - - OutputStream outStream; - if (outputFile != null) { - outStream = new FileOutputStream(outputFile); - } else { - outStream = getContentResolver().openOutputStream(outputUri); - } + // Input + boolean exportSecret = data.getBoolean(EXPORT_SECRET, false); + String outputFile = data.getString(EXPORT_FILENAME); + Uri outputUri = data.getParcelable(EXPORT_URI); - ImportExportOperation importExportOperation = new ImportExportOperation(this, new ProviderHelper(this), this); - Bundle resultData = importExportOperation - .exportKeyRings(publicMasterKeyIds, secretMasterKeyIds, outStream); + boolean exportAll = data.getBoolean(EXPORT_ALL); + long[] masterKeyIds = exportAll ? null : data.getLongArray(EXPORT_KEY_RING_MASTER_KEY_ID); - if (mActionCanceled.get() && outputFile != null) { - new File(outputFile).delete(); - } - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData); - } catch (Exception e) { - sendErrorToHandler(e); + // Operation + ImportExportOperation importExportOperation = new ImportExportOperation(this, new ProviderHelper(this), this); + ExportResult result; + if (outputFile != null) { + result = importExportOperation.exportToFile(masterKeyIds, exportSecret, outputFile); + } else { + result = importExportOperation.exportToUri(masterKeyIds, exportSecret, outputUri); } + // Result + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); + } else if (ACTION_IMPORT_KEYRING.equals(action)) { try { 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 649a74641..7492d95b2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java @@ -29,6 +29,7 @@ import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.ExportResult; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.ProviderHelper; @@ -127,19 +128,10 @@ public class ExportHelper { if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { // get returned data bundle - Bundle returnData = message.getData(); - - int exported = returnData.getInt(KeychainIntentService.RESULT_EXPORT); - String toastMessage; - if (exported == 1) { - toastMessage = mActivity.getString(R.string.key_exported); - } else if (exported > 0) { - toastMessage = mActivity.getString(R.string.keys_exported, exported); - } else { - toastMessage = mActivity.getString(R.string.no_keys_exported); - } - Toast.makeText(mActivity, toastMessage, Toast.LENGTH_SHORT).show(); + Bundle data = message.getData(); + ExportResult result = data.getParcelable(ExportResult.EXTRA_RESULT); + result.createNotify(mActivity).show(); } } }; diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 4a973f9e4..0b0f0b4fd 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -940,6 +940,30 @@ <string name="msg_crt_upload_success">"Successfully uploaded key to server"</string> + <plurals name="msg_import"> + <item quantity="one">"Importing key"</item> + <item quantity="other">"Importing %d keys"</item> + </plurals> + <string name="msg_import_error">"Import operation failed!"</string> + <string name="msg_import_partial">"Import operation successful, with errors!"</string> + <string name="msg_import_success">"Import operation successful"</string> + + <plurals name="msg_export"> + <item quantity="one">"Exporting one keys"</item> + <item quantity="other">"Exporting %d keys"</item> + </plurals> + <string name="msg_export_all">"Exporting all keys"</string> + <string name="msg_export_public">"Exporting 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_success">"Export operation successful"</string> + <string name="msg_del_error_empty">"Nothing to delete!"</string> <string name="msg_del_error_multi_secret">"Secret keys can only be deleted individually!"</string> <plurals name="msg_del"> |