Merge branch 'development' into detached-sigs-api

Conflicts:
	OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
This commit is contained in:
Dominik Schürmann 2015-01-27 09:46:42 +01:00
commit e8780b4410
13 changed files with 188 additions and 58 deletions

View File

@ -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) {

View File

@ -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) {

View File

@ -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;
}

View File

@ -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) {

View File

@ -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),

View File

@ -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();

View File

@ -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;

View File

@ -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 {

View File

@ -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);
ArrayList<ParcelableKeyRing> list = data.getParcelableArrayList(IMPORT_KEY_LIST);
ParcelableFileCache<ParcelableKeyRing> cache =
new ParcelableFileCache<>(this, "key_import.pcl");
// 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);
// Either list or cache must be null, no guarantees otherwise.
ImportKeyResult result = list != null
? importExportOperation.importKeyRings(list, keyServer)
: importExportOperation.importKeyRings(cache, keyServer);
// 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);
}
// Special: make sure new data is synced into contacts
ContactSyncAdapterService.requestSync();
// Result
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
} catch (Exception e) {
sendErrorToHandler(e);
}
// Result
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
break;
}
case ACTION_SIGN_ENCRYPT:
try {

View File

@ -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();

View File

@ -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;

View File

@ -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>

@ -1 +1 @@
Subproject commit 76d7b17f114ef180fdbe6852d8897e8b9a4bca8b
Subproject commit edcc61970f24bb3601af26d89f49d384e7a3aff6