diff options
13 files changed, 187 insertions, 57 deletions
diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java index 008edcda4..b8205a792 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java @@ -438,6 +438,60 @@ public class PgpEncryptDecryptTest { } + @Test + public void testForeignEncoding () throws Exception { + String plaintext = "ウィキペディア"; + byte[] plaindata = plaintext.getBytes("iso-2022-jp"); + + { // some quick sanity checks + Assert.assertEquals(plaintext, new String(plaindata, "iso-2022-jp")); + Assert.assertNotEquals(plaintext, new String(plaindata, "utf-8")); + } + + byte[] ciphertext; + { // encrypt data with a given passphrase + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayInputStream in = new ByteArrayInputStream(plaindata); + + InputData data = new InputData(in, in.available()); + Builder b = new PgpSignEncrypt.Builder( + Robolectric.application, + new ProviderHelper(Robolectric.application), + null, // new DummyPassphraseCache(mPassphrase, 0L), + data, out); + + b.setEncryptionMasterKeyIds(new long[]{ mStaticRing1.getMasterKeyId() }); + b.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128); + // this only works with ascii armored output! + b.setEnableAsciiArmorOutput(true); + b.setCharset("iso-2022-jp"); + SignEncryptResult result = b.build().execute(); + Assert.assertTrue("encryption must succeed", result.success()); + + ciphertext = out.toByteArray(); + } + + { // decryption with provided passphrase should yield the same result + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayInputStream in = new ByteArrayInputStream(ciphertext); + InputData data = new InputData(in, in.available()); + + PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out, null, null, null); + b.setPassphrase(mKeyPhrase1); + DecryptVerifyResult result = b.build().execute(); + Assert.assertTrue("decryption with provided passphrase must succeed", result.success()); + Assert.assertArrayEquals("decrypted ciphertext should equal plaintext bytes", + out.toByteArray(), plaindata); + Assert.assertEquals("charset should be read correctly", + "iso-2022-jp", result.getCharset()); + Assert.assertEquals("decrypted ciphertext should equal plaintext", + new String(out.toByteArray(), result.getCharset()), plaintext); + Assert.assertNull("signature be empty", result.getSignatureResult()); + } + + } + private PgpDecryptVerify.Builder builderWithFakePassphraseCache ( InputData data, OutputStream out, final String passphrase, final Long checkMasterKeyId, final Long checkSubKeyId) { 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 cd8a33c20..3f3e0a432 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java @@ -30,6 +30,7 @@ import org.sufficientlysecure.keychain.keyimport.KeybaseKeyserver; import org.sufficientlysecure.keychain.keyimport.Keyserver; import org.sufficientlysecure.keychain.keyimport.Keyserver.AddKeyException; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; +import org.sufficientlysecure.keychain.operations.results.ConsolidateResult; import org.sufficientlysecure.keychain.operations.results.ExportResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; @@ -44,9 +45,12 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.LogTyp 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.service.ContactSyncAdapterService; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.ParcelableFileCache; +import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; import org.sufficientlysecure.keychain.util.ProgressScaler; import java.io.BufferedOutputStream; @@ -58,6 +62,7 @@ import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.Iterator; +import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; /** An operation class which implements high level import and export @@ -123,6 +128,35 @@ public class ImportExportOperation extends BaseOperation { } } + public ImportKeyResult importKeyRings(List<ParcelableKeyRing> entries, String keyServerUri) { + + Iterator<ParcelableKeyRing> it = entries.iterator(); + int numEntries = entries.size(); + + return importKeyRings(it, numEntries, keyServerUri); + + } + + public ImportKeyResult importKeyRings(ParcelableFileCache<ParcelableKeyRing> cache, String keyServerUri) { + + // get entries from cached file + try { + IteratorWithSize<ParcelableKeyRing> it = cache.readCache(); + int numEntries = it.getSize(); + + return importKeyRings(it, numEntries, keyServerUri); + } catch (IOException e) { + + // Special treatment here, we need a lot + OperationLog log = new OperationLog(); + log.add(LogType.MSG_IMPORT, 0, 0); + log.add(LogType.MSG_IMPORT_ERROR_IO, 0, 0); + + return new ImportKeyResult(ImportKeyResult.RESULT_ERROR, log); + } + + } + public ImportKeyResult importKeyRings(Iterator<ParcelableKeyRing> entries, int num, String keyServerUri) { updateProgress(R.string.progress_importing, 0, 100); @@ -131,9 +165,7 @@ public class ImportExportOperation extends BaseOperation { // If there aren't even any keys, do nothing here. if (entries == null || !entries.hasNext()) { - return new ImportKeyResult( - ImportKeyResult.RESULT_FAIL_NOTHING, log, 0, 0, 0, 0, - new long[]{}); + return new ImportKeyResult(ImportKeyResult.RESULT_FAIL_NOTHING, log); } int newKeys = 0, oldKeys = 0, badKeys = 0, secret = 0; @@ -293,6 +325,16 @@ public class ImportExportOperation extends BaseOperation { position++; } + // Special: consolidate on secret key import (cannot be cancelled!) + if (secret > 0) { + setPreventCancel(); + ConsolidateResult result = mProviderHelper.consolidateDatabaseStep1(mProgressable); + log.add(result, 1); + } + + // Special: make sure new data is synced into contacts + ContactSyncAdapterService.requestSync(); + // convert to long array long[] importedMasterKeyIdsArray = new long[importedMasterKeyIds.size()]; for (int i = 0; i < importedMasterKeyIds.size(); ++i) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java index d81577102..86b37fea6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java @@ -41,6 +41,9 @@ public class DecryptVerifyResult extends OperationResult { OpenPgpSignatureResult mSignatureResult; OpenPgpMetadata mDecryptMetadata; + // This holds the charset which was specified in the ascii armor, if specified + // https://tools.ietf.org/html/rfc4880#page56 + String mCharset; public long getKeyIdPassphraseNeeded() { return mKeyIdPassphraseNeeded; @@ -84,6 +87,14 @@ public class DecryptVerifyResult extends OperationResult { mDecryptMetadata = decryptMetadata; } + public String getCharset () { + return mCharset; + } + + public void setCharset(String charset) { + mCharset = charset; + } + public boolean isPending() { return (mResult & RESULT_PENDING) == RESULT_PENDING; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ImportKeyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ImportKeyResult.java index 1dd038a23..91b53fd78 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ImportKeyResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ImportKeyResult.java @@ -83,6 +83,10 @@ public class ImportKeyResult extends OperationResult { mImportedMasterKeyIds = source.createLongArray(); } + public ImportKeyResult(int result, OperationLog log) { + this(result, log, 0, 0, 0, 0, new long[] { }); + } + public ImportKeyResult(int result, OperationLog log, int newKeys, int updatedKeys, int badKeys, int secret, long[] importedMasterKeyIds) { 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 d025727b5..5b28f0f5a 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 @@ -564,6 +564,7 @@ public abstract class OperationResult implements Parcelable { MSG_DC_ASKIP_NO_KEY (LogLevel.DEBUG, R.string.msg_dc_askip_no_key), MSG_DC_ASKIP_NOT_ALLOWED (LogLevel.DEBUG, R.string.msg_dc_askip_not_allowed), MSG_DC_ASYM (LogLevel.DEBUG, R.string.msg_dc_asym), + MSG_DC_CHARSET (LogLevel.DEBUG, R.string.msg_dc_charset), MSG_DC_CLEAR_DATA (LogLevel.DEBUG, R.string.msg_dc_clear_data), MSG_DC_CLEAR_DECOMPRESS (LogLevel.DEBUG, R.string.msg_dc_clear_decompress), MSG_DC_CLEAR_META_FILE (LogLevel.DEBUG, R.string.msg_dc_clear_meta_file), @@ -654,6 +655,7 @@ public abstract class OperationResult implements Parcelable { MSG_IMPORT_FINGERPRINT_ERROR (LogLevel.ERROR, R.string.msg_import_fingerprint_error), MSG_IMPORT_FINGERPRINT_OK (LogLevel.DEBUG, R.string.msg_import_fingerprint_ok), MSG_IMPORT_ERROR (LogLevel.ERROR, R.string.msg_import_error), + MSG_IMPORT_ERROR_IO (LogLevel.ERROR, R.string.msg_import_error_io), MSG_IMPORT_PARTIAL (LogLevel.ERROR, R.string.msg_import_partial), MSG_IMPORT_SUCCESS (LogLevel.OK, R.string.msg_import_success), diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java index b58df085f..6c987f484 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java @@ -234,6 +234,22 @@ public class PgpDecryptVerify extends BaseOperation { boolean symmetricPacketFound = false; boolean anyPacketFound = false; + // If the input stream is armored, and there is a charset specified, take a note for later + // https://tools.ietf.org/html/rfc4880#page56 + String charset = null; + if (in instanceof ArmoredInputStream) { + for (String header : ((ArmoredInputStream) in).getArmorHeaders()) { + String[] pieces = header.split(":", 2); + if (pieces.length == 2 && "charset".equalsIgnoreCase(pieces[0])) { + charset = pieces[1].trim(); + break; + } + } + if (charset != null) { + log.add(LogType.MSG_DC_CHARSET, indent, charset); + } + } + // go through all objects and find one we can decrypt while (it.hasNext()) { Object obj = it.next(); @@ -550,6 +566,7 @@ public class PgpDecryptVerify extends BaseOperation { log.add(LogType.MSG_DC_OK_META_ONLY, indent); DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log); + result.setCharset(charset); result.setDecryptMetadata(metadata); return result; } @@ -647,6 +664,7 @@ public class PgpDecryptVerify extends BaseOperation { new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log); result.setDecryptMetadata(metadata); result.setSignatureResult(signatureResultBuilder.build()); + result.setCharset(charset); return result; } @@ -807,7 +825,7 @@ public class PgpDecryptVerify extends BaseOperation { while ((ch = fIn.read()) >= 0) { bOut.write(ch); if (ch == '\r' || ch == '\n') { - lookAhead = readPassedEOL(bOut, ch, fIn); + lookAhead = readPastEOL(bOut, ch, fIn); break; } } @@ -824,7 +842,7 @@ public class PgpDecryptVerify extends BaseOperation { do { bOut.write(ch); if (ch == '\r' || ch == '\n') { - lookAhead = readPassedEOL(bOut, ch, fIn); + lookAhead = readPastEOL(bOut, ch, fIn); break; } } while ((ch = fIn.read()) >= 0); @@ -836,7 +854,7 @@ public class PgpDecryptVerify extends BaseOperation { return lookAhead; } - private static int readPassedEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn) + private static int readPastEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn) throws IOException { int lookAhead = fIn.read(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java index 5282deca4..060db96b4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java @@ -83,6 +83,7 @@ public class PgpSignEncrypt extends BaseOperation { private boolean mDetachedSignature; private String mOriginalFilename; private boolean mFailOnMissingEncryptionKeyIds; + private String mCharset; private byte[] mNfcSignedHash = null; private Date mNfcCreationTimestamp = null; @@ -121,6 +122,7 @@ public class PgpSignEncrypt extends BaseOperation { this.mNfcCreationTimestamp = builder.mNfcCreationTimestamp; this.mOriginalFilename = builder.mOriginalFilename; this.mFailOnMissingEncryptionKeyIds = builder.mFailOnMissingEncryptionKeyIds; + this.mCharset = builder.mCharset; } public static class Builder { @@ -149,6 +151,7 @@ public class PgpSignEncrypt extends BaseOperation { private byte[] mNfcSignedHash = null; private Date mNfcCreationTimestamp = null; private boolean mFailOnMissingEncryptionKeyIds = false; + private String mCharset = null; public Builder(Context context, ProviderHelper providerHelper, Progressable progressable, InputData data, OutputStream outStream) { @@ -215,6 +218,11 @@ public class PgpSignEncrypt extends BaseOperation { return this; } + public Builder setCharset(String charset) { + mCharset = charset; + return this; + } + /** * Also encrypt with the signing keyring * @@ -286,6 +294,10 @@ public class PgpSignEncrypt extends BaseOperation { if (mVersionHeader != null) { armorOut.setHeader("Version", mVersionHeader); } + // if we have a charset, put it in the header + if (mCharset != null) { + armorOut.setHeader("Charset", mCharset); + } out = armorOut; } else { out = mOutStream; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index 6c36e1ab8..e1d15e2d3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -563,6 +563,11 @@ public class OpenPgpService extends RemoteService { } } + String charset = pgpResult.getCharset(); + if (charset != null) { + result.putExtra(OpenPgpApi.RESULT_CHARSET, charset); + } + result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); return result; } else { 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 bcb5da277..515711d1f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -403,49 +403,28 @@ public class KeychainIntentService extends IntentService implements Progressable break; } - case ACTION_IMPORT_KEYRING: + case ACTION_IMPORT_KEYRING: { - try { - - // Input - String keyServer = data.getString(IMPORT_KEY_SERVER); - Iterator<ParcelableKeyRing> entries; - int numEntries; - if (data.containsKey(IMPORT_KEY_LIST)) { - // get entries from intent - ArrayList<ParcelableKeyRing> list = data.getParcelableArrayList(IMPORT_KEY_LIST); - entries = list.iterator(); - numEntries = list.size(); - } else { - // get entries from cached file - ParcelableFileCache<ParcelableKeyRing> cache = - new ParcelableFileCache<>(this, "key_import.pcl"); - IteratorWithSize<ParcelableKeyRing> it = cache.readCache(); - entries = it; - numEntries = it.getSize(); - } - - // Operation - ImportExportOperation importExportOperation = new ImportExportOperation( - this, providerHelper, this, mActionCanceled); - ImportKeyResult result = importExportOperation.importKeyRings(entries, numEntries, keyServer); - - // Special: consolidate on secret key import (cannot be cancelled!) - if (result.mSecret > 0) { - // TODO move this into the import operation - providerHelper.consolidateDatabaseStep1(this); - } + // Input + String keyServer = data.getString(IMPORT_KEY_SERVER); + ArrayList<ParcelableKeyRing> list = data.getParcelableArrayList(IMPORT_KEY_LIST); + ParcelableFileCache<ParcelableKeyRing> cache = + new ParcelableFileCache<>(this, "key_import.pcl"); - // Special: make sure new data is synced into contacts - ContactSyncAdapterService.requestSync(); + // Operation + ImportExportOperation importExportOperation = new ImportExportOperation( + this, providerHelper, this, mActionCanceled); + // Either list or cache must be null, no guarantees otherwise. + ImportKeyResult result = list != null + ? importExportOperation.importKeyRings(list, keyServer) + : importExportOperation.importKeyRings(cache, keyServer); - // Result - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); - } catch (Exception e) { - sendErrorToHandler(e); - } + // Result + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); break; + + } case ACTION_SIGN_ENCRYPT: try { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java index 83ba64ce2..78333a8d8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java @@ -41,6 +41,8 @@ import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ShareHelper; +import java.io.UnsupportedEncodingException; + public class DecryptTextFragment extends DecryptFragment { public static final String ARG_CIPHERTEXT = "ciphertext"; @@ -194,7 +196,18 @@ public class DecryptTextFragment extends DecryptFragment { byte[] decryptedMessage = returnData .getByteArray(KeychainIntentService.RESULT_DECRYPTED_BYTES); - mText.setText(new String(decryptedMessage)); + String displayMessage; + if (pgpResult.getCharset() != null) { + try { + displayMessage = new String(decryptedMessage, pgpResult.getCharset()); + } catch (UnsupportedEncodingException e) { + // if we can't decode properly, just fall back to utf-8 + displayMessage = new String(decryptedMessage); + } + } else { + displayMessage = new String(decryptedMessage); + } + mText.setText(displayMessage); pgpResult.createNotify(getActivity()).show(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java index 9d1e8468c..144a16a48 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java @@ -39,18 +39,6 @@ import java.util.ArrayList; public class ImportKeysListLoader extends AsyncTaskLoader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> { - public static class NonPgpPartException extends Exception { - private int mCount; - - public NonPgpPartException(int count) { - this.mCount = count; - } - - public int getCount() { - return mCount; - } - } - final Context mContext; final InputData mInputData; diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 6c4ba7b08..dfb675a37 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -924,6 +924,7 @@ <string name="msg_dc_askip_no_key">"Data not encrypted with known key, skipping…"</string> <string name="msg_dc_askip_not_allowed">"Data not encrypted with allowed key, skipping…"</string> <string name="msg_dc_asym">"Found block of asymmetrically encrypted data for key %s"</string> + <string name="msg_dc_charset">"Found charset header: '%s'"</string> <string name="msg_dc_clear_data">"Processing literal data"</string> <string name="msg_dc_clear_decompress">"Unpacking compressed data"</string> <string name="msg_dc_clear_meta_file">"Filename: %s"</string> @@ -1022,6 +1023,7 @@ <string name="msg_import_fingerprint_ok">"Fingerprint check OK"</string> <string name="msg_import_merge">"Merging retrieved data"</string> <string name="msg_import_error">"Import operation failed!"</string> + <string name="msg_import_error_io">"Import operation failed due to i/o error!"</string> <string name="msg_import_partial">"Import operation successful, with errors!"</string> <string name="msg_import_success">"Import operation successful!"</string> diff --git a/extern/openpgp-api-lib b/extern/openpgp-api-lib -Subproject 76d7b17f114ef180fdbe6852d8897e8b9a4bca8 +Subproject edcc61970f24bb3601af26d89f49d384e7a3aff |